Part 2 of 7 · Quote follow-up series ~4 min read

How a sent quote gets tracked

The system only follows up on what’s in the list. So the first job is making sure the list actually reflects every quote you’ve sent. There are three ways a quote gets in: somebody types a row in the Drive sheet, somebody BCCs the sent-quote email to a dedicated address, or your quoting tool posts a small message when a quote is marked sent. The first one is obvious. The other two exist because in real life nobody types a row in a sheet for the quote they just emailed thirty seconds ago.

Key takeaways

  • Three intake lanes feed one list: the Drive sheet, an inbox lane, and a webhook lane.
  • Inbox emails are read by Bedrock Haiku 4.5, which proposes a row from the sent-quote message.
  • Every proposed row goes to the rep’s inbox for one-tap approval before it lands in the list.
  • Quoting tools that support webhooks can post a quote the moment it’s marked sent.
  • The Drive sheet stays the canonical store. The other lanes are conveniences that write into it.

Three lanes into one list

Three intake lanes funnel into one quote list A diagram with three vertical lane columns at the top and a single unified row at the bottom. Lane one, Drive sheet: somebody types a row directly into the Google Sheet that holds the open-quote list; the drive-sync Lambda mirrors the sheet to S3 every 15 minutes, and the timer reads from there. Lane two, Inbox BCC: somebody BCCs the sent quote to a dedicated address, quotes-at-your-company; SES writes the raw MIME to S3; a parser Lambda calls Bedrock Haiku 4.5 to extract customer, contact email, quote number, amount, and expiry; the proposed row gets emailed to the rep with Approve and Edit links; on approve, the row is added to the Drive sheet via the Sheets API. Lane three, Webhook: when a quote is marked sent in the quoting tool, the tool posts a small JSON message to a Lambda Function URL; the same proposal-and-approval flow runs before the row lands in the sheet. All three lanes converge on the same Drive sheet, which the drive-sync Lambda keeps mirrored to S3 for the timer to read. A note at the bottom: the Drive sheet stays the source of truth — the other lanes are conveniences that propose rows for it. Lane 1 · manual Drive sheet • Somebody types a row directly • drive-sync mirrors to S3 every 15 min • Timer reads from S3 • Source of truth stays in Drive Lane 2 · SES + Bedrock Inbox BCC • BCC sent quote to quotes-address • SES writes MIME to S3 • Haiku 4.5 reads it; proposes a row • Inbox one-tap approve → sheet Lane 3 · on send Webhook • Mark a quote sent in the quoting tool • Tool posts JSON to a Function URL • Same proposal flow to inbox as Lane 2 • On approve → added to sheet Drive quote sheet (source of truth) customer · email · quote no. · amount · sent date · expiry · owner · link drive-sync mirrors it to S3 every 15 min — timer reads from S3 to timer, daily check The Drive sheet stays the source of truth — the other lanes are conveniences that propose rows for it.
Fig 2. Three lanes converge on one Drive sheet. The sheet is the source of truth; the inbox lane and the webhook lane are conveniences that propose rows for human approval. The drive-sync Lambda mirrors the sheet to S3 so the timer can read it without hitting Drive on every check.

Lane 1: the Drive sheet itself

The simplest lane. Open the quote sheet in Drive, add a row, save. The columns are short: customer name, contact email, quote number, amount, the date you sent it, the date it expires, the owner, and a link to the quote PDF. A small Lambda — drive-sync — runs every fifteen minutes, exports the sheet as plain CSV via the Drive API, and writes it to s3://qf-quotes-source/quotes.csv if the sheet has changed since the last sync. The timer reads from S3, not Drive directly. That keeps Drive API calls predictable and gives you S3 versioning for free, so a bad bulk-edit can be rolled back in one click.

This lane covers the cases where you already sent the quote, you know the amount and the expiry, and you can spend thirty seconds typing it in. Most quotes go in this way if your quoting workflow is mostly email.

Lane 2: inbox BCC (the lane most teams actually use)

Set up a dedicated inbound address — something like quotes@your-company.com — via Amazon SES. When you email a quote to a customer, add that address as a BCC. The system takes it from there. SES writes the raw MIME to s3://qf-raw-mime/. The S3 PUT triggers a parser Lambda. The Lambda walks the MIME tree, pulls the email body and any attached quote PDF, and gets the text it needs.

Then a Bedrock Haiku 4.5 call reads the text and emits a structured row: customer name, contact email (the “To” line of your original email), quote number, amount, sent date (the message date), and an expiry if the quote states one. The model prompt is short: “Extract a row for the quote list. Return JSON only. Mark each field with a confidence score. Do not invent an amount or a date that isn’t in the text.” The output goes to a small email back to the rep who sent the quote: the proposed row, the confidence per field, and three links — approve, edit, discard. On approve, a Lambda writes the row to the Drive sheet via the Sheets API. On edit, the rep gets a short form pre-filled with the proposal. On discard, the message is logged and the email moved to a discarded prefix in S3 for audit.

The reason every proposed row goes to a person first is simple: a quote the model misread is worse than a quote that never made it into the list at all. The misread one will quietly nudge the wrong customer about the wrong amount.

Lane 3: webhook from your quoting tool

Some teams send quotes from a dedicated tool — an invoicing app, a proposal builder, a CRM. Many of those can post a small message (a webhook) when a quote is marked sent. Forcing those teams to also type rows in a sheet is a fight you don’t need to have on day one.

Lane 3 accepts that message at a Lambda Function URL. The payload usually already has the customer, the amount, and the quote number, so the parser does very little — it normalizes the fields and runs the same proposal flow as Lane 2, so a person still confirms before the row lands. The Function URL checks a shared secret on every request so a stray post can’t inject a fake quote. Once approved, the row is in the list and the timer picks it up on the next check.

Webhook import is the most opt-in of the three lanes. A team that doesn’t use it loses nothing; a team that does avoids retyping things their quoting tool already knows.

Why the list stays the source of truth

Three lanes in, but only one place where the timer actually looks. That’s a deliberate constraint. If two lanes both wrote directly to the timer’s state, every “why did this nudge go out?” question would mean checking three places. Funneling everything through the Drive sheet means there is exactly one row per quote, and any rep can read or edit any of it without learning a new tool. The convenience lanes are first-class for getting quotes in, but they always pass through the sheet on the way.

Next post: how the timer actually reads the list, computes days since sent and days to expiry, and picks one of four moves.

All posts