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.
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
#expiresget 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#expiresget 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