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.
Trigger conditions
- Email lands at any of the 5 GFS routing addresses (handled by CF Email Routing).
- Manual re-triage of an email that got misclassified.
- Replay of an email after a triage logic change (rare; used for backfills).
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.
The 3 beats
-
01
Log inbound email
INSERT into
inbound_email_logwith email_uuid, from, to, subject, body, classification, extracted_entities, and the workflow_routed_to target. -
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. -
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.
What's different after the workflow runs
- Every email is in
inbound_email_logwith a workflow_routed_to value. - The downstream workflow has a fresh run row tied back to email_uuid.
- Mike sees the email on /intake.html with classification + entity context.
- A draft response is waiting if the email needed one.
- reflexion_log carries an
email_triagetagged row with classification.
What can go wrong and how to recover
Mike re-classifies on /intake.html. The original log row keeps the AI's classification + Mike's correction for next-time training.
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.
Logged with severity=warn. Surfaces on intake.html for manual triage — most often a new vendor that needs adding via new_ingredient_vendor_onboard.
Logged with classification=spam, no downstream dispatch, no response draft. Counts toward weekly anomaly trend if volumes spike.
Adjacent workflows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Workflow contract | workflow_definitions WHERE workflow_type='inbound_email_triage' |
| Routing source | Cloudflare Email Routing → src/email.ts |
| Classifier | AI prompt in src/email.ts → classify_intent |
| Log table | inbound_email_log |
| 5 mailboxes | bids@, ar@, vendors@, specs@, general@ |
| Reflexion tag | email_triage,classification:<cls> |
| Risk level | 2 |
| Expected duration | ~5 min |
| Trigger | event · sources=cf_email_routing |