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
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