Part 1 of 7 · Document expiry watcher series ~5 min read

A document expiry watcher on AWS for a few dollars a month

A small business has more renewals than anyone keeps in their head. The vendor contract that auto-renews on May 31 unless someone gives 60 days’ notice. The cyber-insurance policy that lapses on the 14th and is no fun to explain to the next prospect who asks about it. The food-handler certificate, the lease, the domain, the SOC 2 audit window, the workers’ comp policy, the three software subscriptions that quietly switched to annual when nobody was watching. This post walks through the design of a small watcher that tracks all of it, pings the right owner with enough context to act, and escalates if nobody does.

Key takeaways

  • Three sources for tracked items: a Drive registry, an inbox forwarding lane, and a calendar import lane.
  • Every item ends in one of four moves on each tick: healthy, first alert, reminder, or escalate.
  • Per-category rules: legal contracts get a 90/60/30/14/3-day chain, insurance gets 60/30/7, software gets 30/14/3.
  • Pings respect quiet hours and your holiday calendar. Acknowledged items stop pinging.
  • Designed on AWS for about $2/month at typical small-business volume.

The whole system on one page

Before any code, here’s the shape of what we’re designing.

System architecture: three sources, three pieces inside AWS At the top, three external boxes in a row. Far left, "Tracked items" — a Google Drive registry sheet listing each contract, certificate, insurance policy, license, lease, and software subscription, plus an inbox forwarding lane and a calendar import lane that add new items to the registry. Centre, "Rules and voice" — a Drive folder with a rules doc covering per-category alert windows, owners, and escalation chain, plus a voice doc with the alert message templates. Far right, "Owners" — the team members responsible for each category; pings land in their Slack DMs or, if no Slack ID is set, their email. Each connects via an arrow to the AWS account container below. Tracked items have an outgoing arrow into AWS. Rules and voice feed in to ground every alert. Owners receive pings with the original item, the days remaining, the renewal cost or contract value, a link to the source document, and an acknowledge button. Inside the AWS account are three components in a row, mirroring the layout above. On the left, the Item intake — receives items from each source, parses inbound contracts via Textract and Bedrock, and writes the cleaned item into the registry. In the middle, the Watcher — runs daily; reads the catalog; computes days-to-expiry per item; picks one of four moves: healthy, first alert, reminder, or escalate. On the right, the Dispatch — sends the alert via Slack or email, respects quiet hours and the holiday calendar, and tracks acknowledgment per item. Internal arrows flow left to right. A note at the bottom reads: every alert leaves with full context — the watcher never just says "something is expiring." Tracked items registry, inbox, calendar Rules and voice windows, owners, templates Owners where alerts land items in grounds alert with context AWS account Item intake parse, normalize, add to registry Watcher picks one of four: healthy, alert, remind, escalate Dispatch Slack or email, respects quiet hours item move Every alert leaves with full context — the watcher never just says “something is expiring.”
Fig 1. Three sources outside, three pieces inside AWS. Items flow in from a Drive registry, an inbox forwarding lane, and a calendar import lane. The Watcher runs daily and picks one of four moves. Dispatch sends the right alert to the right person at the right time.

What you set up once (the outside)

  • Tracked items. A Google Sheet in a Drive folder, one row per item: name, category (contract, certificate, insurance, license, lease, software, registration), owner email, vendor, expiry date, renewal cost, contract value, and a link to the source document. You can fill it in once and forget it; new items can also enter via two other lanes covered in Part 2 — an inbox-forwarding lane (forward a contract PDF to a dedicated address and the watcher proposes a row for one-tap approval) and a calendar import lane (events tagged in Google Calendar with #expires get pulled in automatically).
  • A rules folder. Two short Google Docs in a Drive folder. The rules doc covers the alert windows for each category — how many days ahead the watcher should ping, and how many times. Legal contracts typically get a 90/60/30/14/3-day chain; insurance gets 60/30/7; software gets 30/14/3. The doc also lists the owner per category (or per individual item, if it overrides), the escalation target if the owner doesn’t acknowledge, the quiet hours, and any holiday calendars to skip. The voice doc holds one alert message template per category — what the Slack DM or email actually says.
  • Owners. The people responsible for each category. Each owner has a Slack member ID (so the alert is a DM, not a public ping) or, if Slack isn’t set up for them, an email address. Pings land with the item name, days remaining, renewal cost, contract value, a link to the source document, and an “Acknowledge” button that stops further pings on that item.

What runs on every tick (the inside)

  • The item intake. Three sources feed the registry. The Drive sheet itself is the canonical store. New items can also be added via the inbox forwarding lane (forward a PDF to expires@your-company.com, the watcher uses Textract to read the PDF and Bedrock Haiku 4.5 to extract name, vendor, expiry, contract value, then drops a one-tap approval card in the rep’s Slack to confirm before the row is added) and the calendar import lane (events tagged #expires get pulled hourly by a small sync Lambda).
  • The watcher. Runs once a day at 8am local. Reads the registry. For each item, computes days-to-expiry. Compares against the per-category window chain in the rules doc. Picks one of four moves. Healthy: more than the first window away — do nothing. First alert: just crossed the first window threshold — ping the owner with full context. Reminder: crossed a subsequent window with no acknowledgment — re-ping, mention when the previous ping went out. Escalate: hit the final window with no acknowledgment — ping the escalation target named in the rules doc; log it. The watcher itself doesn’t call a model on the daily tick — the move logic is plain Python.
  • Dispatch. Reads the voice doc, formats the alert message for the chosen move and category, and sends it. Slack DMs go through an incoming-webhook URL. Email goes through SES outbound. Both honor quiet hours (no pings between 6pm and 8am local by default) and the holiday calendar (no pings on configured days). Every dispatch writes a row in DynamoDB so the next day’s tick can tell whether the owner acknowledged. A weekly digest summarizes everything that pinged that week, plus what’s coming up. A monthly summary writes a board-ready paragraph: count by category, total contract value at risk, longest-overdue items.

In plain words

Your cyber-insurance policy expires June 14. The renewal premium is $4,200 and the broker needs two weeks to bind cover. The owner is your office manager Maria. The watcher pings her in Slack on April 15 (60 days out): “Cyber-insurance renewal — $4,200 at last quote, broker needs ~2 weeks — expires June 14. [link to policy PDF]” with an Acknowledge button. Maria’s busy that week, doesn’t open it. May 15 (30 days out) she gets a reminder: “Cyber-insurance — 30 days left, last ping April 15.” She acknowledges this time and books a call with the broker. The watcher stops pinging her. On May 28 the broker comes back with new terms; Maria updates the policy in the Drive registry with a new expiry of June 14, 2027 and a new premium. The watcher rolls forward and the cycle starts again next year.

The cost of running this is about $2 a month at SMB volume. The cost of not running it is the one missed cyber-insurance lapse that takes the wind out of a sales conversation, or the SOC 2 audit window everyone forgot about, or the auto-renew clause nobody read.

Design rules that shaped every decision

  • Every alert ships with full context — item, days remaining, renewal cost, link to the source. The owner never has to dig.
  • Four moves, always. Healthy, first alert, reminder, escalate. There is no fifth.
  • Quiet hours and holidays are respected. Pings are a finite resource; bad timing burns them.
  • Acknowledge stops further pings on that item until the next chain. A renewal updates the registry and resets the chain.
  • The registry lives in Drive. Adding an item, changing an owner, or shifting an expiry doesn’t need a deploy.
  • Every dispatch is logged. Audit a renewal next year and you can see every ping that went out.

Why this shape

Most teams track renewals in one of three places: a spreadsheet that nobody opens, a calendar invite that gets dismissed, or somebody’s head. The spreadsheet works until it doesn’t — one missed update and the whole thing goes stale. The calendar invite is the worst kind of false comfort: it pings on the day, with no context, when there’s no longer time to negotiate. And the head, of course, fails the moment the person who held it goes on holiday or leaves the company.

The setup above moves the source of truth into a doc the team already edits, but adds a small system that looks at that doc every day and acts only when something needs acting on. Pings come early enough to do something. They include enough context that the owner doesn’t have to go find the PDF. They escalate cleanly when the owner is out. And they stop the moment somebody says “got it.” The watcher is invisible most days; visible only on the days it actually matters.

The next four posts walk through each piece in turn: how an item gets tracked, how the watcher knows when to alert, how an alert finds the right person, and how an item gets renewed and the cycle restarts. One diagram per post. A cost breakdown and a final engineering reference at the end.

All posts