DEV Community

Abe
Abe

Posted on

Answering service for electricians: what the intake has to capture before the callback

Hey, I'm Abe, founder of OnCrew. We build AI phone intake for HVAC, plumbing, electrical, and roofing shops. This post is about a corner of that work I think generalizes if you are designing any contractor-facing telephony layer: what an answering service for electricians actually has to capture before a callback can be useful, and where naive intake breaks down. I'll keep it technical and skip the marketing.

Most generic "phone agent" demos online are built for small business reception. Name, callback, message, done. For electrical work, that intake is both unsafe and lossy. The reason: electrical service spans a wider risk band than almost any other trade, from "ceiling fan install next Tuesday" to "panel buzzing and faint smell of burning." The intake has to know enough to tell those apart, and the post-call handoff has to route them differently.

Let me walk through how we model this in practice, and what I'd want anyone building in this space to think about before they ship.

Why trade-specific intake is its own design problem

If you have ever built a chat or voice agent over an LLM, you know the temptation is to write one big system prompt and hope the model handles every case. That works in low-stakes domains. It does not work for electrical, for two reasons.

  1. Electrical-emergency language is unusual and partial. Callers often do not know the right term. They say "the wall outlet got hot" or "there's a clicking sound from the basement box" or "the lights went brown for a second." A general agent will reply politely and book a callback. A trade-tuned intake recognizes those as panel, arc, and brownout signals, and follows up specifically.

  2. Life-safety branches must be deterministic, not improvised. If a caller says "I smell something burning," the system has to do a small number of specific things in a specific order. Reaching that branch by free-form LLM reasoning is not acceptable. We treat it as a hard rule above the model.

So in practice, the intake is a hybrid. An LLM-driven dialogue grounded by a structured slot-filling schema, with a small set of deterministic escalation rules that take priority over the model's natural conversational flow.

The minimum intake schema

Roughly what we try to capture on every call. I'll write it as a typed sketch so it is easy to translate into whatever your stack is.

type Intake = {
  caller: {
    name: string
    callback_number: string
    relationship: "owner" | "tenant" | "property_manager" | "other"
  }
  location: {
    address_line1: string
    unit_or_suite?: string
    city: string
    access_notes?: string  // gate codes, dogs, parking
  }
  issue: {
    description_verbatim: string  // caller's own words, not normalized
    category?:
      | "panel"
      | "outlet_or_switch"
      | "lighting"
      | "ev_charger"
      | "generator"
      | "new_construction"
      | "unknown"
    power_state: "on" | "partial" | "off" | "unknown"
    visible_damage: boolean | "unknown"
    smell_or_smoke: boolean | "unknown"
    sound: "buzzing" | "clicking" | "popping" | "none" | "other" | "unknown"
    injuries_reported: boolean
    occupants_present: boolean | "unknown"
  }
  scheduling: {
    preferred_window?: string
    flexibility: "asap" | "today" | "this_week" | "flexible"
  }
  urgency_flag:
    | "life_safety"
    | "same_day"
    | "next_business_day"
    | "routine"
  raw_transcript_url: string
  recording_url?: string
}
Enter fullscreen mode Exit fullscreen mode

A few choices worth flagging:

  • description_verbatim is preserved before any normalization. If you reduce the caller's words to a dropdown, you lose the signal the dispatcher uses to confirm urgency in the morning.
  • urgency_flag is derived, not asked. The caller does not pick it. The intake derives it from the issue slots and a small set of trigger phrases.
  • "unknown" is a first-class value everywhere. Forcing booleans into yes/no when the caller does not know produces false signal and bad routing.

The triage state machine

The reason intake fails for electricians is almost always at the triage step, not the capture step. So this is the part I'd encourage anyone building in the contractor space to take seriously.

We model triage as a small state machine that runs in parallel with slot-filling. Roughly:

                          (any trigger phrase: smoke, burning,
                           sparking, shock, downed line,
                           panel buzzing, exposed wire, fire)
                                        |
                                        v
   [initial] --> [gathering] ------> [life_safety_branch]
                     |
                     | (no triggers, but panel,
                     |  partial power, outlet hot,
                     |  EV charger smoking)
                     v
                  [same_day_branch]
                     |
                     | (work request, no urgency signals)
                     v
                  [routine_branch]
Enter fullscreen mode Exit fullscreen mode

Each branch has its own continuation prompt, its own required slots, and its own handoff behavior. The life-safety branch in particular does three things the others do not:

  1. It tells the caller, in plain language, that for active fire, smoke, or shock, they should call 911 immediately, and for a downed power line they should call the utility's outage number. We do not replace those numbers. We do not claim to.
  2. It still finishes intake in parallel, because the dispatcher will need the address and details regardless.
  3. It triggers a higher-priority handoff to the on-call workflow (page, SMS, ring group, whatever the shop has configured) with the urgency flag visible.

I want to be specific about something. The intake is not a diagnostic tool. It does not tell the caller whether it is safe to flip a breaker back on. It does not tell them whether the outlet is okay. It captures details and flags urgency. The shop's licensed electricians decide the rest. Any system that pretends to do more than that with a homeowner on the line is taking on liability it cannot back up.

Handoff design

The handoff is where most AI phone systems quietly fall apart. The call ends, the recording lands somewhere, and nothing happens until a human opens a dashboard. For after-hours electrical, that is the same as voicemail.

Three patterns we have found useful:

  • Per-shop routing config. Every shop sets, in plain config, what happens at each urgency level. Routine calls go to email or the CRM only. Same-day calls fan out to a dispatch SMS group during business hours and to the on-call phone after hours. Life-safety calls alert the on-call electrician immediately, regardless of clock. Hardcoding any of this is a mistake. Different shops want different thresholds.

  • Idempotent webhook fan-out. When the intake completes, we POST a structured payload to every configured destination, with a stable call id so retries are safe. CRMs, FSM tools, dispatch boards, and a Slack or Teams channel all sit on the same webhook contract. Retries are exponential. Failures alert.

  • Transcript before summary. The shop owner gets the structured summary first, but the raw transcript and recording are always one click away. When an ambiguity comes up the next morning, the summary is not the source of truth. The recording is. A surprising number of disputes resolve by listening to thirty seconds of audio.

Failure modes worth designing around

A non-exhaustive list of cases that break naive implementations:

  • Repeat callers during storm spikes. During a wind event, call volume can jump several times normal in under an hour. Your intake has to be stateless enough to handle bursts, and your handoff has to dedupe by address so the dispatcher does not see the same outage twice.

  • Wrong-number and prank calls. If you do not filter these at the intake layer, every one becomes a "lead" in the CRM and the value of the CRM data collapses. We classify and drop, but keep the recording for audit.

  • Callers in another language. Detect early and switch the intake. Do not force a Spanish-speaking caller through an English script. You lose the job and produce a bad experience.

  • Carrier handoffs and ringback. If the shop's existing number is forwarded conditionally, you need to handle "the line was already ringing" gracefully. Otherwise you queue duplicate intakes on the same call.

  • PII in transcripts. Recordings and transcripts contain addresses, names, and sometimes credit card spelling. Treat them like the sensitive data they are. Encrypt at rest, scope access, and keep a retention policy you can defend.

What this means if you are evaluating one

If you are building or comparing an answering service for electricians, here are the questions I would ask, regardless of vendor:

  1. What is the schema you capture, and is urgency_flag derived or asked?
  2. Are life-safety branches deterministic, or are you trusting the LLM to improvise around fire and shock?
  3. Where is the handoff contract documented, and is it idempotent?
  4. Can the shop owner configure routing per urgency level without filing a support ticket?
  5. Do you preserve verbatim language and raw recordings, or only summaries?

Generic voice agents will not get those right by accident, because they were not designed for this trade. The reason we built OnCrew the way we did is that picking up the phone is the easy part. Capturing the right details, classifying urgency without overclaiming, and routing the result through a workflow the shop actually trusts is where this work lives.

For context on the OnCrew side: trial is 14 days, no charge today, cancel anytime. Pricing is included-call tiers with $0.99 per call on overage. The product page linked above covers the trade-specific details.

If you are building something adjacent in contractor telephony or field-service automation and want to compare schemas or triage logic, I am happy to talk shop in the comments.

Top comments (0)