Wiki · Workflow companion

Inbound email triage

Email arrives at one of the 5 GFS mailboxes (bids@, ar@, vendors@, specs@, general@). Classify intent, extract entities, route to the right downstream workflow, log everything, and stage a draft response for Mike's review.

Stub · Contract present, runner stub-only
What this is

Inbound email triage

Inbound email triage is the front door for the platform. Every email that hits one of the five Cloudflare Email Routing mailboxes flows through here first. Without this workflow, the other workflows wouldn't know they had work to do.

The cascade is deliberately small: classify, extract entities, route, log, draft response. Each step is cheap and read-only. The real work happens downstream in the workflow this dispatches to (bid_amendment_arrives, customer_invoice_dispute, vendor_cost_update, etc).

Risk level 2 (low). Misclassification is recoverable — Mike sees every triaged email on /intake.html and can re-route.

When to use it

Trigger conditions

Heuristic

Unknown senders get severity=warn on the not_spam check, not blocked. Mike sees them on intake.html and decides — this catches new vendors and new customers that haven't been added yet.

Step-by-step what happens

The 3 beats

  1. 01

    Log inbound email

    INSERT into inbound_email_log with email_uuid, from, to, subject, body, classification, extracted_entities, and the workflow_routed_to target.

    Writes inbound_email_log
    Time ~80ms
    Kind d1_write
    Status stub
  2. 02

    Route to downstream workflow

    Based on classification, dispatch: bid_amendment → bid_amendment_arrives; invoice_dispute → customer_invoice_dispute; vendor_cost → vendor_cost_update; spec_sheet → new_assembly_item. Other classifications surface for manual handling.

    Writes workflow_runs (downstream)
    Time ~50ms dispatch + downstream
    Kind dispatch_workflow
    Status stub
  3. 03

    Draft response (conditional)

    If classification is not spam and the email requires a response, AI drafts via propose_email_to_customer. Mike approves before send.

    Writes outbound_email_log
    Time ~3s
    Kind hitl_email_draft
    Status stub
    Note conditional · requires_response
Outcomes

What's different after the workflow runs

Log row
Persisted
≤ 2s
Routed
Yes
≤ 5s downstream
Draft response
Conditional
requires_response
Surface on intake
Yes
real-time
Failure modes

What can go wrong and how to recover

Misclassification

Mike re-classifies on /intake.html. The original log row keeps the AI's classification + Mike's correction for next-time training.

CF Email Routing webhook fails

Email gets bounced back to sender by CF. No log row exists. Mike's recovery path is forwarding the email to a manual intake endpoint.

Unknown sender

Logged with severity=warn. Surfaces on intake.html for manual triage — most often a new vendor that needs adding via new_ingredient_vendor_onboard.

Spam / no-action email

Logged with classification=spam, no downstream dispatch, no response draft. Counts toward weekly anomaly trend if volumes spike.

Related

Adjacent workflows + diagrams

For developers

Code paths + invariants

ConcernWhere
Workflow contractworkflow_definitions WHERE workflow_type='inbound_email_triage'
Routing sourceCloudflare Email Routing → src/email.ts
ClassifierAI prompt in src/email.ts → classify_intent
Log tableinbound_email_log
5 mailboxesbids@, ar@, vendors@, specs@, general@
Reflexion tagemail_triage,classification:<cls>
Risk level2
Expected duration~5 min
Triggerevent · sources=cf_email_routing
// Dispatch map (R550) const dispatch = { bid_amendment: 'bid_amendment_arrives', invoice_dispute: 'customer_invoice_dispute', vendor_cost: 'vendor_cost_update', spec_sheet: 'new_assembly_item', }; await runner.invoke(dispatch[classification], { email_uuid });