Part 4 of 7 · Sentiment monitor series ~5 min read

How an angry mention reaches a human

The reader stored a score and updated the trend. Now the reporter has to decide: does anyone need to hear about this right now? Get it wrong in one direction and a furious customer sits unseen for a day. Get it wrong in the other and you ping the owner’s phone for every mildly grumpy comment until they mute you. Two triggers decide when an alert fires, and four small guardrails sit between the trigger and the actual ping landing — so the alert is loud when it should be and silent when it shouldn’t.

Key takeaways

  • Two triggers: a single mention at or below the mood floor, or a sharp drop in the average.
  • Both triggers are plain Python over the stored scores — no model decides whether to alert.
  • A de-bounce stops the same bad event from firing ten alerts in an hour.
  • Quiet hours hold a non-urgent alert to morning; a true crash still goes straight through.
  • Every alert lists the angriest item first, with a link to the original. The monitor never replies.

Four guardrails on every alert

Four guardrails between a trigger and the delivered alert A horizontal flow diagram. On the far left, a "Trigger fired" box: the reporter detected one of two events — a single mention at or below the mood floor, or the rolling average dropping faster than the configured slope. Four guardrail gates sit in a row to the right, each drawn as a vertical bar. Gate 1: De-bounce — checks whether an alert for the same event already went out inside the cool-down window; if so, this firing is folded into the existing alert instead of starting a new one, so one bad afternoon doesn't produce ten pings. Gate 2: Quiet hours — checks the local time against the rules-doc quiet-hours window; a routine alert outside business hours is deferred to the next morning via an EventBridge Scheduler one-off, but a severe crash (score far below the floor, or a very steep drop) is marked urgent and bypasses the hold. Gate 3: Rank worst-first — gathers the mentions behind the trigger, sorts them most-negative first, and picks the single angriest one to quote at the top so a human reads the worst before anything else. Gate 4: Compose — fills the alert template from the voice doc with the trend direction, the count of negative mentions, the worst quote, the reason line, and a link straight to the original. After all four gates pass, the alert ships through SNS to email and an optional text. A note at the bottom: the monitor never replies — the alert ends at a human's inbox. Trigger fired angry mention or sharp drop Gate 1 De- bounce same event already alerted? cool-down window if so, fold in, don’t re-ping Gate 2 Quiet hours local time in business window? routine → hold to morning; crash → now Gate 3 Rank worst-first gather the mentions in it sort most- negative first, pick the worst Gate 4 Compose + context voice template for the trigger + trend, count, worst quote, link to original Deliver — SNS to email (always) and a text (optional) worst mention quoted first · link to the original · no reply button every alert logged to DDB sm-alerts — the de-bounce reads it next time Every gate is a deterministic check — and the alert ends at a human’s inbox, never a reply.
Fig 4. Four guardrails between a trigger and the delivered alert. De-bounce repeated firings. Hold routine alerts for quiet hours but let a crash through. Rank the mentions worst-first. Compose with full context. Then ship via SNS — and stop there, at a human.

The two triggers

An alert fires on one of two conditions, and both are plain Python over the stored scores. The first is the single angry mention: any one mention that scores at or below the mood floor set in the rules doc (default: the most-negative band). One furious review can matter on its own, even on a day when everything else is fine — so it gets its own trigger, no averaging required.

The second is the sharp drop: the rolling average falling faster than the configured slope over the trailing window. This is the one that catches the quiet bad week — no single mention is extreme, but five mildly negative ones in two days pull the average down hard. The slope trigger sees the shape of the change even when no individual mention would trip the floor. Together the two cover both failure modes: the loud one and the slow one. The model is nowhere in this decision; it only produced the scores the math runs on.

Gate 1: de-bounce

One bad event tends to produce many mentions close together. Without a guard, each one re-trips the trigger and fires another alert — ten pings for one problem, which trains the owner to ignore all of them. Gate 1 checks the sm-alerts table: has an alert for this same event already gone out inside the cool-down window (default a few hours)? If yes, the new firing is folded into the existing alert — the count goes up, a fresh quote may be added — but no second ping is sent. The owner gets one alert that grows, not a stream of duplicates.

Gate 2: quiet hours

The rules doc has a quiet-hours window (default 9pm to 7am). A routine alert that fires inside that window is held: the reporter creates a one-off EventBridge Scheduler rule that re-runs the dispatch at the next morning’s start, and exits without sending. The owner wakes to it instead of being woken by it.

There’s one exception, and it’s important. A true crash — a mention scored far below the floor, or a very steep drop — is marked urgent and bypasses the hold entirely. The judgment here is simple: a normal dip can wait until morning; a customer publicly melting down at midnight, or a sudden cliff in the average, is exactly the thing you’d want to be woken for. The threshold for “urgent” is in the rules doc too, so you tune how easily the monitor is allowed to break quiet hours.

Gate 3: rank worst-first

Before composing anything, Gate 3 gathers the mentions behind the trigger — the one furious mention, or all the negative ones inside the window that dragged the average down — and sorts them most-negative first. The single angriest one is picked to quote at the top of the alert. This is the human-in-the-loop part made concrete: the worst item is always the first thing the reader sees, so the person deciding what to do reads the genuine low point before skimming the rest. No alert buries the worst mention three lines down.

Gate 4: compose with full context, then ship

The voice doc has a template per trigger type. The reporter fills it: which way the trend is moving, how many negative mentions are involved, the angriest quote, its one-line reason, and a link straight to the original so a human can go read it in context. The filled alert ships through SNS — an email always, and an optional text for the urgent ones. The email has no reply button, no “respond” action, nothing that touches your public accounts. It ends at the inbox.

Every alert sent writes a row to sm-alerts with the event, the time, and what was quoted. The next firing’s de-bounce reads that row. That’s the loop that keeps one event to one growing alert.

Why it always stops at a human

The firmest rule in the whole system lives in this post: the monitor reports, it does not reply. It would be easy to add a button that posts a draft response, and tempting — but a wrong public reply from an automated system, sent at the worst possible moment to the angriest possible customer, is exactly the kind of damage the monitor exists to prevent. So the design draws a hard line. The monitor’s job is to put the worst mention in front of the right person, fast, with enough context to act — and then get out of the way. The reply is a human’s, every time.

Next post: the weekly pulse — the calm, scheduled counterpart to the instant alert, summarizing the whole week’s mood in one short email.

All posts