Part 1 of 7 · Churn predictor series ~5 min read

A churn predictor on AWS for a few dollars a month

Customers rarely quit with a bang. They quit quietly. The regular who used to order every week now orders every month. The account that opened three angry support tickets and then went silent. The user who hasn’t logged in since February. By the time someone notices, they’re already gone — and winning a customer back after they leave costs far more than a friendly call while they’re still wavering. This post walks through the design of a small predictor that watches the simple signals you already have, scores who looks at-risk in plain language, and hands the owner a short weekly list of people to reach out to — with the reason for each.

Key takeaways

  • Three sources for the customer list: a Drive sheet, an order-feed import, and a support-inbox lane.
  • Every customer lands in one of four bands each week: steady, watch, at-risk, or churning.
  • The score is plain arithmetic — a weighted sum of a few signals, all spelled out in the rules doc.
  • The owner gets a short weekly list, capped at five names, each with a plain reason. A human reaches out.
  • Designed on AWS for about $2/month at typical small-business volume. It never messages customers itself.

The whole system on one page

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

System: three sources, three pieces inside AWS At the top, three external boxes in a row. Far left, "Customer signals" — a Google Drive list of customers plus an order-feed import lane (last order, order pace) and a support-inbox lane that reads each ticket's mood, all feeding the list the predictor reads. Centre, "Rules and voice" — a Drive folder with a rules doc covering the signal weights, the score bands, owners, and the weekly cap, plus a voice doc with the reason and list templates. Far right, "Owners" — the account managers responsible for each customer; the weekly at-risk list lands in their Slack DM or, if no Slack ID is set, their email. Each connects via an arrow to the AWS account container below. Customer signals have an outgoing arrow into AWS. Rules and voice feed in to ground every score and every reason. Owners receive a short weekly list with the at-risk customers, the days since last order, the plain reason each name was flagged, the customer's monthly value, and three outcome buttons. Inside the AWS account are three components in a row, mirroring the layout above. On the left, the Signals intake — receives customers from each source, reads support-ticket mood via Bedrock, and writes the cleaned signals into the list. In the middle, the Scorer — runs weekly; reads the list; turns each signal into points using the weights; adds them up; picks one of four bands: steady, watch, at-risk, or churning. On the right, the Hand-off — builds the capped weekly list, writes a plain reason per name, sends it via Slack or email, and tracks the owner's outcome per customer. Internal arrows flow left to right. A note at the bottom reads: the predictor only flags and explains — it never messages a customer on its own. Customer signals list, orders, support Rules and voice weights, bands, templates Owners where the list lands signals in grounds list with reasons AWS account Signals intake orders, mood, add to the list Scorer picks one of four: steady, watch, at-risk, churning Hand-off Slack or email, five names with reasons signal band The predictor only flags and explains — it never messages a customer on its own.
Fig 1. Three sources outside, three pieces inside AWS. Signals flow in from a Drive list, an order-feed import, and a support-inbox lane. The Scorer runs weekly and picks one of four bands. The Hand-off sends a short list of at-risk customers, with a reason for each, to the right owner.

What you set up once (the outside)

  • Customer signals. A Google Sheet in a Drive folder, one row per customer: name, owner email, last order date, order count, support mood (a number from sour to happy), last login date, plan, and monthly value. You fill it in once with what you have; the numbers that change often — last order, order pace, mood — get refreshed by two other lanes covered in Part 2: an order-feed import (your store or billing tool drops a daily export and the predictor updates the order columns) and a support-inbox lane (forward or copy support tickets to a dedicated address and the predictor reads each one’s mood into a simple score).
  • A rules folder. Two short Google Docs in a Drive folder. The rules doc holds the signal weights — how many points a long gap since the last order is worth, how much a drop in order pace counts, how much a sour support mood adds, how much a login gap matters — and the cut-offs for the four bands. It also lists the owner per customer or per segment, the weekly cap (default five names), and any customers to never flag (your biggest account’s owner may want to handle that one personally). The voice doc holds the templates for the weekly list and the one-line reason per customer.
  • Owners. The account managers responsible for each customer. Each owner has a Slack member ID (so the list is a private DM, not a public post) or, if Slack isn’t set up, an email address. The weekly list lands with each at-risk customer’s name, days since last order, the plain reason they were flagged, the monthly value at stake, and three buttons — Reached out, Won back, Lost.

What runs on every weekly run (the inside)

  • The signals intake. Three sources feed the list. The Drive sheet itself is the canonical store. The order-feed import updates last-order and order-pace columns from a daily export your store or billing tool already produces. The support-inbox lane reads each forwarded ticket: a Bedrock Haiku 4.5 call rates the customer’s mood on a simple scale (sour, flat, happy) and writes it back to the row, so a string of frustrated tickets nudges the score without anyone hand-typing it.
  • The scorer. Runs once a week, Monday at 8am local. Reads the list. For each customer, it turns each signal into points using the weights in the rules doc — days since last order, drop in order pace versus their own history, sour support mood, login gap — and adds them up. The total lands the customer in one of four bands. Steady: low score, nothing to do. Watch: rising score, noted but not surfaced yet. At-risk: crossed the threshold — eligible for this week’s list. Churning: high score, already drifting hard — top of the list. The scorer calls no model; the arithmetic is plain Python so anyone can check the math.
  • The hand-off. Reads the voice doc, takes the highest-scoring at-risk customers up to the weekly cap, and writes a one-line plain reason for each — “no order in 47 days, down from weekly; two sour support tickets last month.” Sends the list as a Slack DM or email to each owner. Every list and every outcome the owner records writes a row in DynamoDB so the next week’s run knows who was already contacted. A monthly summary writes an owner-ready paragraph: how many were flagged, how many were reached, how many were won back, how many were lost.

In plain words

Your customer Greenfield Cafe used to order beans every week. The order pace slipped to monthly in March, and last week they opened a support ticket that read as frustrated. The owner of that account is your sales rep, Dan. On Monday the predictor puts Greenfield near the top of Dan’s weekly list: “Greenfield Cafe — $380/mo — no order in 34 days, down from weekly; one sour support ticket. Reach out?” with three buttons. Dan calls Greenfield on Tuesday, learns a delivery was late, fixes it, and taps Won back. The predictor resets Greenfield to steady and won’t flag it again unless the signals slip once more. If Dan had done nothing, Greenfield would have quietly stopped ordering — and Dan would have found out two months later when someone asked why revenue dipped.

The cost of running this is about $2 a month at SMB volume. The cost of not running it is the steady, invisible leak of customers who would have stayed if anyone had called in time.

Design rules that shaped every decision

  • The system only flags and explains. A human reaches out. Nothing is ever sent to a customer automatically.
  • The score is plain arithmetic, not a black box. Every flag ships with the exact reason that tripped it.
  • Four bands, always. Steady, watch, at-risk, churning. There is no fifth.
  • The weekly list is capped (default five). A list of forty names is a list nobody acts on.
  • The customer list lives in Drive. Changing a weight, an owner, or a do-not-flag customer doesn’t need a deploy.
  • Every outcome is logged. Review a save or a loss next quarter and the trail is there.

Why this shape

Most small businesses find out about churn from the revenue chart, which is the worst possible place — it tells you a customer left weeks after the moment you could have done anything about it. The fancier answer, a machine-learning model trained on your data, is overkill for a small business and worse than useless if nobody trusts its output: a score of “0.83 risk” with no reason attached gets ignored, and rightly so.

The setup above keeps the data where the team already keeps it, but adds a small system that looks at the simple signals every week and does plain, checkable arithmetic. The list is short enough to act on. Each name comes with a reason a human can agree or disagree with. And the act of reaching out — the part that actually saves the customer — stays with a person who can read the room. The predictor is the early-warning bell, not the firefighter.

The next four posts walk through each piece in turn: how an at-risk customer gets watched, how a churn score gets calculated, how the at-risk list reaches the right owner, and how a win-back gets tracked once they act. One diagram per post. A cost breakdown and a final engineering reference at the end.

All posts