Part 2 of 7 · Deadline reminder series ~4 min read

How a deadline gets on the calendar

The system only reminds you about what’s on the calendar. So the first job is making sure the calendar actually reflects every recurring obligation your business has. There are three ways a deadline gets in: somebody types a row in the Drive sheet, somebody forwards a notice to a dedicated address, or somebody puts an event on their Google Calendar with a small tag. The first one is obvious. The other two exist because in real life nobody types a row in a sheet for the renewal notice that just arrived in the mail.

Key takeaways

  • Three intake lanes feed one calendar: the Drive sheet, an inbox-forwarding lane, and a calendar import.
  • Inbound notices are parsed by Textract; Bedrock Haiku 4.5 reads the text and proposes a row.
  • Every parsed row goes to the owner’s Slack for one-tap approval before it lands in the calendar.
  • Calendar events tagged #deadline get pulled hourly via the Google Calendar API.
  • The Drive sheet stays the canonical store. The other lanes are conveniences that write into it.

Three lanes into one calendar

Three intake lanes funnel into one calendar 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 deadline calendar; the drive-sync Lambda mirrors the sheet to S3 every 15 minutes, and the checker reads from there. Lane two, Inbox forwarding: somebody forwards a renewal notice or a tax letter to a dedicated address, deadlines-at-your-company; SES writes the raw MIME to S3; a parser Lambda runs Textract on the PDF, then calls Bedrock Haiku 4.5 to extract name, type, next due date, repeat interval, and owner; the proposed row gets posted to the owner's Slack with Approve and Edit buttons; on approve, the row is added to the Drive sheet via the Sheets API. Lane three, Calendar import: events on someone's Google Calendar with the hashtag #deadline in the description get pulled hourly by a calendar-sync Lambda using the Calendar API; the same proposal-and-approval flow runs in Slack 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 checker 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 • Checker reads from S3 • Source of truth stays in Drive Lane 2 · SES + Textract Inbox forwarding • Forward notice to deadlines-address • SES writes MIME to S3 • Textract reads PDF; Haiku 4.5 extracts row • Slack one-tap approve → sheet Lane 3 · hourly poll Calendar import • Tag a calendar event with #deadline • calendar-sync polls hourly via Calendar API • Same proposal flow to Slack as Lane 2 • On approve → added to sheet Drive deadline sheet (source of truth) name · type · owner · repeat · next due · lead time · reference link drive-sync mirrors it to S3 every 15 min — checker reads from S3 to checker, daily run 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 calendar lane are conveniences that propose rows for human approval. The drive-sync Lambda mirrors the sheet to S3 so the checker can read it without hitting Drive on every run.

Lane 1: the Drive sheet itself

The simplest lane. Open the deadline sheet in Drive, add a row, save. The columns are short: name, type, owner email, repeat interval (monthly, quarterly, yearly, or a custom pattern), next due date, lead time, and a link to any reference. A small Lambda — drive-sync — runs every fifteen minutes, exports the sheet as plain CSV via the Drive API, and writes it to s3://dr-calendar-source/calendar.csv if the sheet has changed since the last sync. The checker 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 know a deadline, you know when it’s due and how often it repeats, and you can spend thirty seconds typing it in. Most known obligations — payroll, the quarterly filing, the annual license — go in this way during the initial setup.

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

Set up a dedicated inbound address — something like deadlines@your-company.com — via Amazon SES. Anyone on the team forwards a renewal notice, a tax letter, or a compliance reminder to that address and the system takes it from there. SES writes the raw MIME to s3://dr-raw-mime/. The S3 PUT triggers a parser Lambda. The Lambda walks the MIME tree to the PDF attachment, runs Amazon Textract on it (Textract reads PDF, PNG, JPEG, and TIFF natively; if somebody forwards a Word document, the parser falls back to python-docx), and gets back the extracted text plus any tables.

Then a Bedrock Haiku 4.5 call reads the text and emits a structured row: name, type, next due date, repeat interval (if the notice states one), and an owner-suggestion based on the “To” line of the original forward. The model prompt is short: “Extract a recurring-deadline row. Return JSON only. Mark each field with a confidence score. Do not invent a date that isn’t in the text.” The output goes to a small Slack interactive message that pings the person who forwarded the email: the proposed row, the confidence per field, and three buttons — approve, edit, discard. On approve, a Lambda writes the row to the Drive sheet via the Sheets API. On edit, the owner gets a fillable modal pre-populated with the proposal. On discard, the message is logged and the PDF moved to a discarded prefix in S3 for audit.

The reason every parsed row goes to a human first is simple: a due date the model misread is worse than a deadline that never made it onto the calendar at all. The misread one will quietly tell you everything is fine until the morning the filing is actually late.

Lane 3: calendar import

Some teams already track obligations on a calendar. The annual report is on the office manager’s calendar. The quarterly safety inspection is on the operations calendar. The franchise-fee due date is on the owner’s calendar. Forcing those teams to also type rows in a sheet is a fight you don’t need to have on day one.

Lane 3 picks up calendar events tagged with #deadline in the description. A small calendar-sync Lambda runs hourly, iterates through the configured Google Calendars (using a service-account credential stored in Secrets Manager), and pulls any events with the tag whose start time is in the future. Each pulled event becomes a proposal in the same Slack flow as Lane 2 — one-tap approve to add to the calendar, with the repeat interval read from the event’s own recurrence rule if it has one. Once approved, the calendar event itself can stay where it is or be deleted; the deadline sheet now owns it.

Calendar 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 they already typed once.

Why the sheet stays the source of truth

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

Next post: how the checker actually reads the calendar, computes days-to-due, and picks one of four moves.

All posts