From "can I get pricing?" to a signed PDF in fifteen minutes
The customer quote workflow is the most-run flow on the platform. Customers email or call asking for pricing on assemblies, often by their own internal SKU. The AI gathers context (who they are, what they typically buy, current cost basis, AR status), proposes line-by-line pricing, Mike reviews, the system renders an HTML artifact + PDF, and the email goes out.
The contract is stored in workflow_definitions WHERE workflow_type='draft_quote' — risk level 2 (low-med), expected duration 15 minutes, three HITL gates at steps 1, 2, and 4.
The defining property: nothing about pricing is invented. Every line traces to pricing_master, assembly_cost_rollup, or an explicit override that Mike typed into chat. The AI narrates; the database decides.
Trigger conditions
- Customer emails
quotes@ororders@asking for pricing on an assembly. - Sales call wraps up and Mike needs to send a follow-up quote.
- RFP comes in via the intake page (Dropbox or upload) and needs a structured response.
- Existing customer wants pricing on a new pack/format of an item they already buy.
- Mike wants to test a price point before formalizing it into the pricing_master.
Customer Z requests pricing on assembly A123
Cardinal Foods (NS customer #1843) emails: "Can you quote A123 — Right Start Foods Breakfast Burrito assembly — for 5,000 cases monthly starting July?"
Mike opens chat, types "Draft quote: Cardinal, A123, 5,000 cases/mo July onwards". The draft_quote workflow starts. Within 90 seconds the AI has pulled Cardinal's customer 360 (30-day order history, AR aging — they're current, their typical bid eligibility), looked up A123's current cost rollup ($3.42 landed), proposed $4.18 per case (22% gross margin per Mike's standard), and rendered a side-by-side comparison.
Mike skims, taps Approve lines, then Approve send. The HTML quote artifact lands at /quote/Q2026-0512, the PDF goes into R2, and the email lands in Cardinal's inbox with a tracked link. Total wall-clock: 11 minutes.
The five beats
-
01
Gather context (auto)
The
get_customer_360chat tool pulls the customer record from the D1 mirror, the trailing-90-day order history, current AR aging buckets, and any flags fromcustomer_health_scores. This becomes the prompt context for the line-builder. -
02
Build lines (HITL)
propose_quotelooks up each assembly's cost inassembly_cost_rollup, applies the customer's negotiated margin band (or the platform default if none), and writes draft rows toquote_lineswithstatus='draft'. A proposed_action row lands in Mike's queue. -
03
Approve lines (HITL)
Mike taps Approve in
/proposed-actions.html. The quote rows flip tostatus='approved'and the atomic claim (R560 pattern) prevents double-approval. Mike can edit individual lines before approving — those edits write back to the samequote_linesrows. -
04
Render PDF (auto)
render_quote_pdfgenerates the HTML artifact (Pages Function at/quote/) and converts to PDF via Browser Rendering. Both land in R2 underquotes/. The HTML link is what gets put in the email body./ -
05
Send to customer (HITL)
A draft email row in
outbound_email_logwithstatus='pending_review'. Mike skims, taps Send, the email goes out via Email Routing. Eventquote.sentis appended to the events ledger. The customer-facing tracked link is logged for open/click tracking.
What's different after the workflow runs
- A row in
quoteswith statussentand a link to the HTML artifact at/quote/. - Per-line audit trail in
quote_linestying back topricing_masterandassembly_cost_rollup. - An outbound email row with
status='sent', message ID, and tracked open/click links. - An event
quote.sentin the events ledger so customer health + sales velocity watchers see it. - Mike's chat history retains the full transcript for next-time recall.
What can go wrong and how to recover
If assembly_cost_rollup doesn't have a row for the requested assembly, the proposer refuses to invent a margin. Mike sees a "no cost basis" error and has to add the cost first (vendor cost update workflow).
If the customer's customer_health_scores.risk_tier is red, the workflow surfaces a warning band but doesn't block. Mike decides; this is a soft guardrail not a hard one.
Browser Rendering occasionally hits 30s. The HTML artifact still works — Mike can send just the link. Re-trigger PDF via POST /admin/quotes/.
Most common quiet failure. The outbound_email_log dashboard has a "pending_review > 24h" filter to catch these. Daily 9am digest highlights them too.
Adjacent workflows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Workflow contract | workflow_definitions WHERE workflow_type='draft_quote' |
| Customer 360 | src/chat_tools/impls.ts get_customer_360 |
| Line builder | src/chat_tools/impls.ts propose_quote |
| PDF render | render_quote_pdf — Browser Rendering binding |
| HTML artifact | pages-functions/quote/[id].ts |
| HITL gates | steps 1, 2, 4 — see hitl_gates_json |
| Cost rollup invariant | never invent margin — refuse if assembly_cost_rollup empty |
| Email send | Cloudflare Email Routing — outbound_email_log first |