Part 1 of 7 · Refund handler series ~5 min read

A refund handler on AWS for a few dollars a month

Refund requests are some of the most stressful email a small business gets. The customer is already unhappy. The person answering is often busy, or new, or both — and the policy lives in a doc nobody has read in months. So replies come out slow, inconsistent, and sometimes wrong: a refund given that the policy says no to, or one refused that the policy clearly allows. This post walks through the design of a small handler that reads each request, checks it against your own written policy, drafts a calm and kind reply, and hands the easy ones to a person for a single tap. It never sends money on its own.

Key takeaways

  • Three sources feed one queue: a help inbox, a contact-form webhook, and a manual paste lane.
  • Every request ends in one of four outcomes: in policy, out of policy, high-value, or not covered.
  • The checker only acts on what your policy actually says — it cites the line it used.
  • Nothing sends money on its own. A person approves every refund with one tap.
  • Designed on AWS for about $3/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, "Refund requests" — the three places a refund request comes from: a help inbox that catches forwarded customer emails, a contact-form webhook, and a manual paste lane for requests that arrived by phone or chat. Centre, "Policy and voice" — a Drive folder with a policy doc covering refund windows, conditions, exceptions, and dollar caps, plus a voice doc with the reply tone and templates. Far right, "Your team" — the people who approve refunds; cards land in their Slack or email. Each connects via an arrow to the AWS account container below. Refund requests have an outgoing arrow into AWS. Policy and voice feed in to ground every decision. The team receives an approve card with the original request, the policy line that applies, the drafted reply, the dollar amount, and an approve button. Inside the AWS account are three components in a row, mirroring the layout above. On the left, the Intake — receives requests from each source, pulls out who, what, and how much, and writes a clean request record. In the middle, the Checker — reads the request; pulls the exact policy passages that apply; decides one of four outcomes: in policy, out of policy, high-value, or not covered. On the right, the Reply — drafts a kind reply in your voice, quotes the policy line used, and routes the card for human approval. Internal arrows flow left to right. A note at the bottom reads: nothing sends money on its own — a person approves every refund. Refund requests inbox, form, paste Policy and voice rules, caps, templates Your team where cards land in grounds card to approve AWS account Intake read who, what, how much Checker picks one of four: in, out, high-value, none Reply drafts a kind reply, routes for approval req call Nothing sends money on its own — a person approves every refund.
Fig 1. Three sources outside, three pieces inside AWS. Requests flow in from a help inbox, a contact-form webhook, and a manual paste lane. The Checker reads each one against your policy and picks one of four outcomes. The Reply piece drafts a kind answer and routes it to a person to approve.

What you set up once (the outside)

  • Refund requests. Three ways a request gets in. A help inbox — customers email or your team forwards an email to a dedicated address. A contact-form webhook — the “request a refund” form on your site posts straight to the handler. And a manual paste lane — for the request that came in by phone or live chat, a rep pastes it into a small form so it gets the same treatment as everything else. All three are covered in Part 2.
  • A policy folder. Two short Google Docs in a Drive folder. The policy doc is your refund policy in plain prose — the return window, the conditions (unused, original packaging, proof of purchase), the exceptions (final-sale items, custom orders), and any dollar caps (“refunds over $200 need a manager”). The voice doc holds the tone and a reply template per outcome — what a kind “yes,” a clear “no,” and a “we need a bit more” actually sound like in your words.
  • Your team. The people who approve refunds. Each gets a card in Slack (or email if Slack isn’t set up) with the original request, the policy line that applies, the drafted reply, the dollar amount, and an “Approve” button. Low-value, clearly in-policy cases get the one-tap card. Out-of-policy or high-value cases go to a named approver with the reason attached.

What runs on every request (the inside)

  • The intake. Three sources feed one queue. The intake reads each incoming request — an email, a form post, or a pasted note — and pulls out the parts that matter: who the customer is, what they bought, the order or receipt reference, how much, and what they’re asking for. It writes a clean request record and drops it on the queue. Bedrock Haiku 4.5 (a small, cheap model) does the reading; the rest is plain code.
  • The checker. The heart of the system. For each request, it pulls the exact policy passages that apply — not the whole policy, just the lines about this kind of case — and decides only from them. In policy: the policy clearly allows it and it’s under the cap — route to a one-tap approve. Out of policy: the policy clearly says no — draft a kind decline that quotes the rule, route to a person to confirm. High-value: allowed, but over the dollar cap — route to a named approver. Not covered: nothing in the policy fits — route to a human to decide, never guessed at. The checker cites the policy line behind every decision.
  • The reply. Reads the voice doc, drafts a reply for the chosen outcome in your tone, and quotes the policy line the checker used so the customer sees the “why,” not just the answer. The draft is attached to the approval card — it is never sent on its own. On approve, the reply goes out via SES and the request is marked resolved. A weekly digest summarizes what came in, what was approved, and what’s still waiting. Every action is logged so a refund can be read back months later.

In plain words

A customer emails: “The blender I bought three weeks ago stopped working. I’d like a refund.” The handler reads it: customer Dana, order #4821, blender, $89, asking for a refund, 21 days since delivery. The checker pulls the policy lines about the 30-day window and the “faulty item” exception, sees the request is inside the window and the item is reported faulty, and lands on in policy. It drafts: “Hi Dana — sorry the blender stopped working. Our policy covers faulty items within 30 days, so we’ll refund the $89 to your original payment. You’ll see it in 5–7 days…” with the policy line quoted underneath. A card lands in the support Slack with that draft and an Approve button. Sam taps Approve. The reply goes out. The whole thing took Sam four seconds, and the answer was right because it came from the actual policy.

The cost of running this is about $3 a month at SMB volume. The cost of not running it is the refund email that sat for two days and turned a fixable problem into a one-star review — or the refund that got approved against policy because nobody had time to check.

Design rules that shaped every decision

  • Nothing sends money on its own. A person approves every refund — this rule has no exceptions.
  • Four outcomes, always. In policy, out of policy, high-value, not covered. There is no fifth.
  • Every decision cites the policy line it came from. No answer without a “why.”
  • Not covered goes to a human. The checker never guesses at a case the policy doesn’t address.
  • The policy lives in Drive. Changing a window, a cap, or an exception doesn’t need a deploy.
  • Every request and decision is logged. Audit a refund next year and you can see exactly why it went out.

Why this shape

Most teams handle refunds in one of three ways: whoever opens the email decides on the spot, a manager has to be pulled in for every one, or there’s a policy doc that’s technically the rule but nobody reads it under pressure. The first is fast but inconsistent — two customers with the same case get different answers depending on who replied. The second is consistent but slow, and burns a manager’s day. The third is the false comfort of having a policy that doesn’t actually shape the replies going out.

The setup above keeps the policy where the team already edits it, but adds a small system that reads that policy on every single request and drafts the reply from it. The easy cases — which are most of them — become a one-tap approve. The hard cases get flagged early, with the policy line and the reason attached, so the person deciding has what they need. And because the money never moves without a human tap, the system can be helpful without ever being dangerous.

The next four posts walk through each piece in turn: how a refund request arrives, how it gets checked against policy, how a reply gets drafted, and how a refund gets approved. One diagram per post. A cost breakdown and a final engineering reference at the end.

All posts