Skip to main content

ACH Leads Integration

Automated intake of leads from ACH (Accident Claims Helpline). ACH emails a lead to a dedicated mailbox; Zoho Flow passes the email into a Zoho Creator custom API function, which stores the raw email, calls the Anthropic Claude API to extract the claim details as structured JSON, and creates an ACH Lead record. The Bookings team actions the lead from a report and uses the Create Claim button to turn it into a Claim.


Components

ComponentTypePurpose
ach-crashclaim@zohomail.com.auZoho Mail mailboxDedicated inbox that receives lead emails from ACH
Zoho FlowAutomationWatches the mailbox and calls the Ach_incoming_email_recieved_from_zoho_mail custom API function with the parsed email
ACH Zoho Mail PayloadZoho Creator formRaw capture of the exact payload Zoho Flow sends in, before any parsing
ACH Incoming EmailZoho Creator formParsed email (Subject/Body/From/To/cc) + full Claude request/response audit trail
ACH Claude ConfigurationZoho Creator formSingle-record settings form holding the Claude prompt, API key, model, and call parameters
Claude (Anthropic API)External APIExtracts structured lead data (JSON) from the email body
ACH LeadZoho Creator formStructured lead record parsed from the Claude JSON response
New ACH Lead ReportZoho Creator reportWork queue where the Bookings team actions leads (Create Claim / Cancel Lead / Update Lead)
Bookings Dashboard → New ACH Leads tileZoho Creator dashboard panelStat tile showing the current count of open ACH Leads (New ACH Lead + Duplicate Lead); links through to the New ACH Lead Report
Claim FormZoho Creator formTarget form, prepopulated from the ACH Lead via the Create Claim button

End-to-End Flow

ACH sends lead email


ach-crashclaim@zohomail.com.au (Zoho Mail)
│ Zoho Flow trigger: new email in inbox

Incoming Email recieved from zoho mail(payload)

├─ [1] Insert into "ACH Zoho Mail Payload" (raw payload audit)
├─ [2] Insert into "ACH Incoming Email" (parsed Subject/Body/From/To/cc)

Claude API Integration for Data Extraction

├─ [3] Load "ACH Claude Configuration" (prompt, key, model, max_tokens, temperature)
├─ [4] Call Anthropic Messages API → structured JSON text
├─ [5] Write Claude_payload / Raw_AI_Response / Usage / Response / AI_Error_* back onto
│ the ACH Incoming Email record

Create ACH Lead from JSON

├─ [6] Clean + parse the Claude JSON, create "ACH Lead" record
├─ [7] Duplicate check (registration this month → email → phone)
│ Status = "New ACH Lead" or "Duplicate Lead" (+ Duplicate_Reason)

Lead appears on New ACH Lead Report (Status == New ACH Lead || Duplicate Lead)

├── [Create Claim] → Claim Form opens prepopulated → user submits
│ → Status = "ACH Claim Created", Claim_Form linked

├── [Cancel Lead] (confirmation prompt) → Status = "ACH Lead Cancelled"
│ → lead drops off this report

└── [Update Lead] → edit/correct lead fields (no status change)

Flow Steps

#StepDetail
1Email receivedACH sends the lead to ach-crashclaim@zohomail.com.au. Zoho Flow's mail trigger fires and calls the custom API function Ach_incoming_email_recieved_from_zoho_mail, passing the email as a payload map (subj, body, from_address, to_address, cc_address).
2Capture raw payloadThe function first inserts the untouched payload into ACH Zoho Mail Payload, then inserts a parsed ACH Incoming Email record (Subject, Body, From, To, cc). The new record's ID is passed forward.
3AI extractionclaude_api_to_extract_html_to_json loads the single ACH Claude Configuration record and sends the email body to Claude.
4Persist AI responseThe Claude request, raw API response, token usage, extracted text, and any error details are written back onto the ACH Incoming Email record.
5Create leadcreate_ach_lead_from_json parses the Claude JSON and creates an ACH Lead record (see mapping table below).
6Duplicate handlingThe new lead is checked against existing leads by vehicle registration (same calendar month), then email, then phone. Status is set to Duplicate Lead on a match, otherwise New ACH Lead.
7QueueThe lead is now visible on the New ACH Lead Report for manual action.

Anthropic Claude Configuration & Usage

The ACH Claude Configuration record supplies everything the extraction call needs: prompt_template (system prompt), API_key, model_version, max_tokens, and temperature — with claude-haiku-4-5-20251001 / 2048 / 0 used as defaults when a field is left blank.

Model version: claude-haiku-4-5-20251001

System prompt (prompt_template):

You are a data extraction assistant. Extract information from the provided HTML email and return ONLY a valid JSON object — no explanation, no markdown, no code blocks.

Always use this exact JSON structure with these exact key names. If a value is not found in the HTML or the field is empty, set it to null.

Return this structure:

{
"Reference": null,
"Repairer used": null,
"Scheduled Drop Off": null,

"Customer Details": {
"Name": null,
"Address": null,
"City": null,
"State of Province": null,
"Country or Region": null,
"Postal Code": null,
"Date of Birth": null,
"Email": null,
"Tel": null
},

"Vehicle Details": {
"Is the car personal or company?": null,
"License category": null,
"License expiry": null,
"Customer Registration": null,
"Vehicle Make": null,
"Vehicle Model": null,
"Vehicle year": null,
"Tow bar?": null
},

"Accident Details": {
"Is the customer the accident vehicle driver?": null,
"Number of vehicles in the accident?": null,
"Accident Date": null,
"Accident Time": null,
"Accident Location": null,
"Car Parked?": null,
"Travelling Lane": null,
"Travelling Speed": null,
"Change Lanes?": null,
"Is this a rear ender?": null,
"Accident Description": null,
"Vehicle Damage Sustained": null,
"Police Event Number": null
},

"At Fault Details": {
"At fault name": null,
"At fault mobile": null,
"Accident Position": null,
"Insurance Policy?": null,
"Address": null,
"Claim Lodged?": null,
"Insurance claim number?": null,
"Insurance Company": null,
"At fault vehicle make": null,
"At fault vehicle model": null,
"At fault vehicle year": null,
"At fault vehicle registration": null,
"Liability confirmed?": null,
"Liability confirmed by?": null,
"Liability description": null,
"Liability date & time": null
},

"Witness Details": {
"Witness name": null,
"Witness phone": null,
"Witness address": null
}
}

Rules :
1. Never change, rename, add, or remove any key.
2. Never guess or infer a value — if it is not explicitly present in the HTML, set it to null.
3. Empty strings (""), whitespace-only values, and placeholder text (e.g. "N/A", "-", "n/a") must also be returned as null.
4. Do not include any text, explanation, or formatting outside the JSON object. No backticks. No markdown. The response must start with { and end with }.
5. Keep date and time values exactly as they appear in the source.
6. Keep Yes/No values exactly as they appear (e.g. "Yes", "No") — do not convert to boolean.
7. Apply the following date and time formatting rules to these specific fields only:

DATE FIELDS — "Accident Date", "Date of Birth", "License expiry":
- Input format: dd/MM/yyyy (e.g. 14/07/2026)
- Output format: dd-MMM-yyyy (e.g. 14-Jul-2026)
- Month must be 3-letter English abbreviation with first letter uppercase (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)

TIME FIELD — "Accident Time":
- Convert any time format to 24-hour HH:mm
- Examples: "13.40pm" → "13:40", "1:40 PM" → "13:40", "09:00 AM" → "09:00", "10:00" → "10:00"

DATETIME FIELDS — "Scheduled Drop Off", "Liability date & time":
- Input format: dd/MM/yyyy HH:mm AM/PM (e.g. 14/07/2026 09:00 AM)
- Output format: dd-MMM-yyyy HH:mm using 24-hour time (e.g. 14-Jul-2026 09:00)
8. TIME FIELD — "Accident Time":
- Convert any time format to 24-hour HH:mm
- If the value contains a time range (e.g. "10:30 - 11:00am", "10.30 - 11.00am"), extract the FIRST time only and ignore everything after the dash
- Examples:
"13.40pm" → "13:40"
"1:40 PM" → "13:40"
"09:00 AM" → "09:00"
"10:00" → "10:00"
"10:30 - 11:00am" → "10:30"
"10.30 - 11.00am" → "10:30"
"9:00am - 10:00am" → "09:00"

claude_api_to_extract_html_to_json builds a request to https://api.anthropic.com/v1/messages:

  • Headers: x-api-key, anthropic-version: 2023-06-01, anthropic-beta: prompt-caching-2024-07-31.
  • System prompt is sent with cache_control: {"type": "ephemeral"} so Anthropic's prompt caching can reuse it across calls, reducing cost/latency since the same instructions are sent for every email.
  • User message is "Extract the claim details from this email:\n\n" + <email body>.

The prompt instructs Claude to return a single JSON object with five top-level sections — Customer Details, Vehicle Details, Accident Details, At Fault Details, Witness Details — which line up directly with the ACH Lead form sections above. The response text is stripped of ```json code fences and newlines before being parsed.

Token usage from the API response (usage) is written to the Usage field on ACH Incoming Email for cost monitoring; the full request/response are kept in Claude_payload / Raw_AI_Response for troubleshooting.


Duplicate check (runs immediately after the lead is created)

Checked in order, first match wins:

  1. Registration — another ACH Lead with the same Customer_Registration, created within the current calendar month → Duplicate Lead, reason "Customer Rego match found in this month data".
  2. Email — another ACH Lead (any time) with the same EmailDuplicate Lead, reason "Customer Email match found".
  3. Phone — another ACH Lead (any time) with the same TelDuplicate Lead, reason "Customer Phone match found".
  4. No match → New ACH Lead.

Create Claim Process

  1. From the New ACH Lead Report, the Bookings team clicks Create Claim on a lead row (create_claim_ach_lead workflow).
  2. This opens the Claim Form with the ACH Lead's ID passed in as input.ACH_Lead.
  3. The Claim Form's on-load script (claims/onload) runs: it looks up the ACH Lead record and prepopulates the Claim Form from it per the mapping table below — including:
    • Setting Lead_Source1 to the Lead Source record for "Accident Claims Helpline".
    • Splitting First_Name_client / First_Name into first/last name fields.
    • Parsing Date_of_Birth and Accident_Date from dd/mm/yyyy or dd-MMM-yyyy text into real dates, and Accident_Time (e.g. "7:40am") into a 24-hour time.
    • Mapping Is_the_car_personal_or_company of "Personal"/"Company" to Registration Type "Private"/"Business".
    • Looking up the at-fault insurer name against Insurer_Form to populate the insurer lookup field, in addition to copying the plain insurer name.
    • Adding a Witness/Police Details row when a witness name or phone is present (witness address is folded into that row's Notes).
  4. The user reviews the prepopulated form, completes any required fields with no source in the ACH Lead (Claim Type, Recovery Type, Relationship to Owner, Transmission, Vehicle Type, rental need details, insurance/renewal info, signatures), and submits.
  5. On successful submission, the Claim Form's on-success script (claims/onsuccess of claim form) runs: it sets the ACH Lead's Status to ACH Claim Created, links the new claim back via Claim_Form, adds an audit note ("New ACH Lead Created - <File Number>"), and opens the newly created claim.

Mapping: ACH Lead → Claim Form

Set automatically by the on-load script, not part of the field-level mapping: Lead_Source1 = the Lead Source record for "Accident Claims Helpline".

Customer Details

ACH Email FieldClaim Form Field
NameDriver First Name
AddressDriver Address → Address Line 1
CityDriver Address → District/City
State of ProvinceDriver Address → State/Province
Country or RegionDriver Address → Country
Postal CodeDriver Address → Postal Code
Date of BirthDriver Date of Birth
EmailDriver Email
TelDriver Phone

Vehicle Details

ACH Email FieldClaim Form Field
Is the car personal or company?Registration Type (Personal → Private, Company → Business)
License category
License expiry
Customer RegistrationRegistration *
Vehicle MakeMake *
Vehicle ModelModel *
Vehicle yearYear *
Tow bar?

Accident Details

ACH Email FieldClaim Form Field
Is the customer the accident vehicle driver?
Number of vehicles in the accident?Number of Vehicles Involved * (1 to 5)
Accident DateDate of Accident *
Accident TimeTime of Accident *
Accident LocationAccident Location *
Car Parked?
Travelling Lane
Travelling Speed
Change Lanes?
Is this a rear ender?
Accident DescriptionAccident Description *
Vehicle Damage SustainedDamage Description *
Police Event Number

At Fault Details

ACH Email FieldClaim Form Field
At fault nameAt Fault Name *
At fault mobileAt Fault Phone Number
Accident PositionOnly captured for the 3rd/4th at-fault party
Insurance Policy?Are you Insured? *
AddressAt Fault Address
Claim Lodged?Has the accident been reported to the at-fault insurer?
Insurance claim number?Claim Number
Insurance CompanyAt Fault Party Insurer Name
At fault vehicle makeAt Fault Vehicle Make
At fault vehicle modelAt Fault Vehicle Model
At fault vehicle yearAt Fault Vehicle Year
At fault vehicle registrationAt Fault Registration
Liability confirmed?Did the at fault driver admit liability?
Liability confirmed by?
Liability descriptionHow was liability confirmed?
Liability date & timeClaims Management form → Date liability accepted

Witness Details

ACH Email FieldClaim Form Field
Witness nameWitness/Police Details row — Type = Witness → Name
Witness phoneWitness/Police Details row → Phone
Witness address

* = required field on the Claim Form.

Not prepopulated (user must complete on the Claim Form)

  • Claim_Type1, Recovery_Type, Rental_Provider, Rental_Recovery_type
  • Relationship_to_Owner, Transmission, Vehicle_Type
  • Rental Need section (Rental_is_my_only_option_because, etc.)
  • Are_you_insured, Policy_Renewal / renewal dates
  • Signatures / Terms & Conditions

New ACH Lead Report

Report on the ACH Lead form: shows records where Status == "New ACH Lead" or "Duplicate Lead", sorted by Added_Time descending. A lead drops off this report once it's cancelled or converted to a claim.

The Bookings Dashboard shows a "New ACH Leads" stat tile alongside the other intake counters (New Claims, New Leads, Pending Validation, etc.), giving the open-lead count at a glance and linking through to this report.

Custom Buttons

ButtonWorkflowBehaviour
Create Claimcreate_claim_ach_leadOpens the Claim Form prepopulated from the lead (see Create Claim Process). On submission, Status becomes ACH Claim Created.
Cancel LeadCancel_Lead_ACHPrompts "Execute the Cancel Lead action for this record?" before running. Sets Status to ACH Lead Cancelled, which removes the lead from this report.
Update LeadUpdate_ACH_leadLets the Bookings team edit/correct lead fields (e.g. fix extraction errors) before creating a claim. Does not change Status.

Lead Status Lifecycle

StatusSet whenSet by
New ACH LeadLead created and no duplicate match foundcreate_ach_lead_from_json
Duplicate LeadLead created and a registration/email/phone match foundcreate_ach_lead_from_json
ACH Lead CancelledUser runs Cancel Lead from the reportCancel_Lead_ACH workflow
ACH Claim CreatedUser completes the prepopulated Claim Form via Create Claimclaims/onsuccess of claim form
┌──────► Duplicate Lead ──┐
Lead created ───┤ ├──► ACH Claim Created
└──────► New ACH Lead ────┤
└──► ACH Lead Cancelled

Error Handling

FailureWhere recordedBehaviour
Claude API call fails / errors in responseAI_Error_Status + AI_Error_Log on the ACH Incoming Email recordNo ACH Lead created; the incoming email record remains for troubleshooting
Claude returns empty/unparseable JSONcreate_ach_lead_from_json returns a failure status; Response still holds the raw textNo ACH Lead created
Function-level exception during the API callAI_Error_Status = "Failed", AI_Error_Log = exception detailNo ACH Lead created

Token usage for every extraction call is recorded in Usage on the ACH Incoming Email record for cost monitoring.


Open Items

  • Whether a Duplicate Lead can still be converted via Create Claim is not explicitly restricted in the code reviewed.

Demo Videos