Thirty days of unsubscribes came back as exactly 100. Seven days of the same account came back as 42
A round number is a tell. orbit_check_deliverability reported exactly 100 unsubscribes over 30 days on a real account and 42 over 7 — and seven days cannot hold 42 while thirty hold 100. Forty-two a week extrapolates to roughly 180, and Braze's own canvas step statistics showed 84 in 14 days. The 100 was the page size. Braze's /email/hard_bounces and /email/unsubscribes default to limit=100, cap at 500 per request, and return no total and no cursor; Orbit passed neither limit nor offset, then reported emails.length as the count. Nothing errored. Any account with more than 100 unsubscribes in the window just got a confident, plausible, permanently-too-low number. Both endpoints are paged now — and three other things in the same tool were wrong in the same direction: thresholds that made the verdict track how long you looked rather than how healthy you were, a recommendation that was busywork, and a health rating that has never been able to see a send total and never said so.
What shipped
•Bug fix — orbit_check_deliverability pages both email event endpoints instead of reading the first 100 rows as the total. It asks for limit=500 and walks an advancing offset until a short page proves the end, which is the only end signal Braze offers — there is no total in the response and no cursor to follow. A 40-page ceiling stops a runaway at 20,000 records, and hitting it sets count_is_exact: false and pushes an explicit TRUNCATED warning saying the number is a floor rather than a total, so a capped count can never again be read as a complete one. hard_bounces and unsubscribes both carry count_is_exact and per_day alongside the count.
•Fix — the health verdict stopped tracking how long you looked. Health and warnings compared raw counts against fixed numbers — 50 bounces, 100 unsubscribes — written for a 30-day window and then applied to every window, so a 90-day lookback almost always read needs_attention and a 7-day one almost always read healthy. Same account, same underlying health, opposite verdict, decided by the days argument. Every threshold is per-day now, so the answer holds at any lookback.
•Behaviour change — the bounce recommendation points at a gap that actually exists. On a high bounce count the tool advised implementing a bounce suppression segment to exclude hard-bounced users from all non-transactional sends. Braze already flags a hard-bounced address on the profile and stops sending to it, so that was busywork dressed as a prerequisite — and it sent a real user off to build one before they pushed back, correctly. It now names the upstream half, email validation at signup and at import, and says plainly that Braze auto-suppresses, because the suppression was never the gap: the bad addresses arriving are.
•New field — health_basis states the limit out loud instead of leaving it to be discovered. These endpoints carry no send total, so the tool is counting events with no denominator and cannot tell a healthy 0.2% bounce rate from an alarming 5% one; a busy account reads needs_attention purely for sending more. The field says exactly that, and points at where the real answer lives — dividing by sends from the canvas or campaign data series, and Google Postmaster Tools for spam complaints, which is what Gmail and Yahoo enforce on and what a bounce count is the wrong instrument for.
•Guard — seven tests hold the paging, in tests/suites/77-deliverability-pagination.test.mjs. The one worth naming checks that an exactly-full final page does not end the walk early, because a stop-on-full-page reading of the same fix reintroduces the original bug at a multiple of 500 instead of at 100. The others cover counting across pages, asking for the maximum page size, declaring truncation rather than under-reporting quietly, a verdict that survives moving from 30 days to 90, and the suppression recommendation staying gone.
•New section — braze-claude-in-chrome-build documents the rebind that actually lands. A duplicated canvas keeps the OLD bodies snapshotted in its message steps and the template picker is fuzzy, so the rebind has to go through the editor: pbcopy the compiled HTML, focus Monaco with monaco.editor.getEditors()[0].focus(), then cmd+A and cmd+V. The focus call is the whole trick — a coordinate click inside the code pane puts the selection on the page instead, so cmd+A takes the whole document and the paste goes nowhere, quietly. 45 KB lands in one shot where typing it times out. The readback needed its own rule: Braze rewrites every href when a step saves, inserting a twelve-character ?lid=, unescaping & to &, and flipping ?email= to &email= on any link that already carried a query string. A sha256 against the template can therefore never match, and a push that worked reads as one that failed. Strip the lid, unescape both sides, then diff — anything left is a real change. Subject and preheader are not in the body; they are set under the step's Sending Settings on the same visit, or they silently stay stale.