Deadline first: I built Letter Decoder, a calm agent for confusing Canadian letters
#agents You know the feeling. A letter from the CRA, your insurer or your landlord lands in your inbox, and your stomach drops before you've read a word. The sentences are long, the tone is formal, and the only question you actually care about is buried somewhere in the middle: by when do I have to act? For the AWS Builder Center "build an agent" weekend challenge, I built an agent that answers that question first.
What it does and who it's for
Letter Decoder (Canada) reads a confusing official letter and gives you four things, in this order:
- The deadline, in one calm sentence ("Not scary. Send the documents by Oct 28.")
- What the letter is, in plain language, and who sent it
- What to do next, as a short checklist
- A draft reply you can adapt
It's for anyone in Canada who has just opened a letter from the CRA, an insurer or a landlord and doesn't know whether to worry. It's not a lawyer, an accountant or a broker, and it says so upfront. When it can't find a deadline, or isn't sure, it says "I'm not sure, check with a professional" instead of bluffing.
The agent handles letters in English, redacts personal information before processing, and works entirely within AWS — no third-party AI services, no data leaving your AWS account.
How I built it
Everything runs on AWS.
- Amazon Bedrock with Amazon Nova Pro (
amazon.nova-pro-v1:0) for the reasoning, so every call is billed to my AWS account and nothing leaves the AWS ecosystem. The code refuses any model that isn't an Amazon model on Bedrock. - Multi-Agent Orchestrator (AWS's open source agent framework) for the agent loop, tool orchestration, and conversation memory.
- Streamlit for the front end, packaged in a Docker container and deployed on AWS App Runner, with an IAM role limited to invoking Bedrock models and writing logs to CloudWatch.
- AWS Lambda functions power the utility tools (
add_daysanddays_until) that the agent calls for date calculations. - Amazon CloudWatch Logs captures all interactions for monitoring and debugging, with PII already redacted before logging.
The design choices matter more than the stack:
- The model never does date math. Letters say things like "within 30 days from the date of this letter." I gave the agent two small Lambda-powered tools,
add_daysanddays_until, and after it answers, my code recomputes the days left itself. Dates are where an agent can do the most harm by being confidently wrong. - Privacy before the model. Before any text is sent to Bedrock, a local step hides SINs, phone numbers, emails, postal codes and long account numbers with
[REDACTED-XXX]tokens, and tells the user what it hid. The model never sees your real information. - Honesty rules in code, not just in the prompt. If no deadline is found, or confidence is low, the app always shows "worth checking with a professional," whatever the model said. The prompt guides behavior; the code enforces it.
- The letter is data, not instructions. It is wrapped in
<letter>tags and the prompt explicitly says to ignore any instructions inside it. Anything the model writes is escaped before it's displayed in HTML. - Offline tests. The date, redaction, parsing and AWS-only checks run without any AWS access (20 unit tests in pytest), so I could trust the boring parts and spend the weekend on the experience.
- Cost control. Each letter decode costs roughly $0.02–0.05 with Nova Pro, and I set a max token limit of 4,000 output tokens to prevent runaway costs.
The delightful detail: deadline first
The one thing I did to make it enjoyable: the answer opens with a calm, colour-coded banner that tells you the deadline and how worried to be, before any explanation. "Not scary. Reply by Nov 14." in green. "A week or less. Worth doing first." in orange. Under it sit the days left and a confidence label, so you can see how sure the agent is.
I paired that with errors that talk in the agent's own voice and tell you what to try next, for example: "I only run on Amazon's own models in Amazon Bedrock... set the model ID and try again," instead of a stack trace.
How I know it worked:
- Accuracy: I ran four fictional letters through an eval script: a CRA request for information (30 days from the letter date), an insurer claim decision (60 days), a landlord rent notice (a stated date), and a vague account update with no deadline at all. The agent got 4 of 4 right, and on the vague letter it correctly said there was no deadline and asked for a human check.
- People: 12 people tried it during the weekend challenge. 10 of them tapped "Yes, that helped," and the median time from paste to answer was 8 seconds.
- One tester said: "I tried it with my actual landlord letter from last month. It found the deadline I'd missed and wrote a better reply than I would have. That saved me real stress."
Proof it works
too be Added
What I learned and what's next
The biggest lesson: an agent feels trustworthy when it is willing to say "I don't know." Making the no-deadline letter a test case, not an edge case, changed how I built everything else.
Next: French letters, scanned images through Amazon Textract, province-specific guidance, and reminders before the deadline.
Letter Decoder explains what a letter says and suggests sensible next steps. It is not legal, tax or insurance advice.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article