[{"data":1,"prerenderedAt":142},["ShallowReactive",2],{"journal-\u002Fjournal\u002Fsent-didnt-mean-what-we-thought":3},{"id":4,"title":5,"body":6,"date":128,"description":129,"draft":130,"extension":131,"meta":132,"navigation":133,"path":134,"seo":135,"stem":136,"tags":137,"__hash__":141},"journal\u002Fjournal\u002Fsent-didnt-mean-what-we-thought.md","\"Sent\" Didn't Mean What We Thought It Meant",{"type":7,"value":8,"toc":117},"minimark",[9,13,16,21,24,27,30,34,37,40,43,46,49,53,56,59,62,66,69,72,75,78,82,85,88,92,95,98,101,105,108,111,114],[10,11,12],"p",{},"An email campaign went out. The dashboard reported 29,566 sent. The provider was rejecting nearly all of them.",[10,14,15],{},"Nothing errored. No alert fired. The number on screen was large, green and wrong, and it was wrong in the most dangerous direction a metric can be wrong in: the reassuring one.",[17,18,20],"h2",{"id":19},"we-were-measuring-the-wrong-event","We were measuring the wrong event",[10,22,23],{},"The bug was in the definition. We counted an email as sent when we handed it to the provider. That's a real event. It's just not the one anyone cares about. Handing a letter to the post office tells you nothing about whether it arrived.",[10,25,26],{},"Delivery outcomes come back later, asynchronously, as bounce and complaint notifications. Count at handoff and never reconcile, and your records permanently reflect the optimistic moment. A campaign reports as sent forever, whatever happened to it.",[10,28,29],{},"Two changes. Sent now means delivered. And when a bounce or complaint arrives, it goes back and reconciles the record it belongs to. The number is allowed to get worse after the fact, because in an asynchronous system that's what honesty looks like.",[17,31,33],{"id":32},"bounces-are-not-a-marketing-problem","Bounces are not a marketing problem",[10,35,36],{},"This is the part that's easy to underrate, and it's why any of the work got funded.",[10,38,39],{},"Deliverability runs on sender reputation. Providers track how much of your mail bounces or gets marked as spam, and past a threshold they quietly start routing you to junk, or refusing you outright. Reputation takes months to build and one bad afternoon to damage.",[10,41,42],{},"It isn't scoped to the campaign that caused it either. The damage lands on transactional mail. Password resets, confirmations, the notifications people are actively waiting for. So a blast to a stale list doesn't just waste a blast. Days later it can degrade the mail your core flows depend on, for users who were never on that list.",[10,44,45],{},"That's the framing that gets this prioritized. Not \"improve campaign metrics.\" Protect a shared asset everything else quietly depends on.",[10,47,48],{},"Which gives you the rule: a suppression list applies to every email, not just campaigns. If an address hard-bounced, it's undeliverable, and that doesn't stop being true because the next message is a password reset. Enforcing suppression only on bulk sends means repeatedly mailing addresses you already know are dead, which is precisely the behaviour reputation systems punish.",[17,50,52],{"id":51},"stop-the-bleeding-automatically","Stop the bleeding automatically",[10,54,55],{},"Once you can see bounces in real time you can act on them in real time.",[10,57,58],{},"We put a circuit breaker on outgoing campaigns. Watch the bounce rate as the send progresses, cross a threshold, cancel the rest. I tested it against seeded lists to find where it actually trips. On a list with roughly 6% bad addresses it halted after around 200 sends. On a ten-thousand-recipient list with 5% known-bad, it cancelled with over 90% of the list unsent.",[10,60,61],{},"Two hundred bad sends is a rounding error. Ten thousand is an incident. The difference is a feature you can build in an afternoon, and you need it because the bad list is usually discovered by sending to it.",[17,63,65],{"id":64},"the-feature-that-shipped-switched-off","The feature that shipped switched off",[10,67,68],{},"Here's the part I find most instructive, and it isn't technical.",[10,70,71],{},"The circuit breaker's configuration keys existed in the database schema. They were read at runtime. The logic behind them worked. They were never surfaced in the settings screen, so nobody operating the product could see the feature existed, let alone turn it on.",[10,73,74],{},"It shipped off by default and unreachable. From the perspective of every person it was built for, it didn't exist.",[10,76,77],{},"I treat this as a completion criterion now rather than polish. If the only way to enable something is an engineer editing the database, you've built a capability, not a feature. That gap is invisible from inside the codebase and total from outside it.",[17,79,81],{"id":80},"one-provider-account-many-customers","One provider account, many customers",[10,83,84],{},"A multi-tenant wrinkle: we send on behalf of many client organizations through one provider account. Bounce notifications all arrive in one stream, and reputation damage from one client's stale list is shared by everyone else's mail.",[10,86,87],{},"Before you can manage that you have to attribute it. Outgoing mail gets stamped with the configuration set it belongs to and inbound notifications are filtered by it, so a bounce traces back to the tenant that generated it instead of landing in an undifferentiated pile. Attribution first, then policy. You can't enforce a per-client threshold on data you can't split.",[17,89,91],{"id":90},"duplicates-are-their-own-reputation-problem","Duplicates are their own reputation problem",[10,93,94],{},"Separately, some users received over a hundred copies of the same event reminder.",[10,96,97],{},"The causes were mundane and common. Reminder jobs bursting after a deploy. A stalled-work poller running far more often than it needed to. A template dispatched once per source instead of once per recipient. None of it exotic. All of it producing the same outcome: a user who marks you as spam.",[10,99,100],{},"A person receiving a hundred emails doesn't file a bug. They hit the spam button, and that button wires directly into the asset you were protecting.",[17,102,104],{"id":103},"on-webhooks-that-carry-consequences","On webhooks that carry consequences",[10,106,107],{},"A design note, stated as a principle rather than a war story.",[10,109,110],{},"If a third-party service posts notifications to your endpoint and those notifications change state, like adding addresses to a suppression list, that endpoint needs to verify the messages came from that service. Signature verification, a check that the signing certificate lives where it should, a replay window.",[10,112,113],{},"The reason: suppression is a denial-of-service primitive pointed at your own users. An endpoint accepting unverified bounce notifications will happily accept forged ones, and a forged bounce means a real person stops getting their password resets. Any webhook whose payload has side effects deserves the same suspicion as a public API that does the same thing, because that's what it is.",[10,115,116],{},"Of everything here, the one I keep coming back to is the breaker that shipped switched off. Cheapest mistake to make, hardest to notice, because from inside the repository it all looked finished.",{"title":118,"searchDepth":119,"depth":119,"links":120},"",2,[121,122,123,124,125,126,127],{"id":19,"depth":119,"text":20},{"id":32,"depth":119,"text":33},{"id":51,"depth":119,"text":52},{"id":64,"depth":119,"text":65},{"id":80,"depth":119,"text":81},{"id":90,"depth":119,"text":91},{"id":103,"depth":119,"text":104},"2026-07-20","A campaign reported 29,566 emails sent while the provider was rejecting nearly all of them. What that taught me about bounce handling, sender reputation, and metrics that fail in the reassuring direction.",false,"md",{},true,"\u002Fjournal\u002Fsent-didnt-mean-what-we-thought",{"title":5,"description":129},"journal\u002Fsent-didnt-mean-what-we-thought",[138,139,140],"engineering","email","product","Xb5JbbzYL1-d5EN1PFdYhpxXZxjCrT6IYsj0RmSTwJo",1787915616666]