惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Troy Hunt's Blog
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
T
Tenable Blog
L
LINUX DO - 热门话题
V
Visual Studio Blog
I
Intezer
Blog — PlanetScale
Blog — PlanetScale
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
Cyber Attacks, Cyber Crime and Cyber Security
F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
Know Your Adversary
Know Your Adversary
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
Netflix TechBlog - Medium
SecWiki News
SecWiki News
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
Project Zero
Project Zero
W
WeLiveSecurity
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Vercel News
Vercel News
S
Securelist
Spread Privacy
Spread Privacy
L
LangChain Blog
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
MongoDB | Blog
MongoDB | Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
罗磊的独立博客
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
The Last Watchdog
The Last Watchdog
Attack and Defense Labs
Attack and Defense Labs
博客园 - 司徒正美
Help Net Security
Help Net Security
L
Lohrmann on Cybersecurity
人人都是产品经理
人人都是产品经理
Forbes - Security
Forbes - Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
博客园 - 【当耐特】
T
Tor Project blog

Buttondown's blog

Email could have been X.400 times better The physicists who convinced Fermilab to send Brazil's emails Better in-app previews Analytics 3.0 Subscriber ID variables Comments! Send latest premium action Automation filtering Free API subscribers Surveys in automations Reply to replies Labels for RSS feeds How Jeremy Singer-Vine curates curious datasets for readers 2023 (and what's next) Email vs web content Sort by engagement Better gift subscriptions How Andy Dehnart built a career reviewing television New email template Email-based automations Opt-in reply tracking Automatic alt text More social network integrations Sort by metadata Overlarge image warnings Automation tag actions Pause emails mid-flight Search tags and automations Gift via automations Subscriber-driving emails Programmatic webhooks Email page views Tag statistics Discord webhook formatting Automatic subscriber cleanup RSS subscriber count Weekly subscriber reports More list columns Customizable list views How Max Voltar turned a side gig into a trusted keyboard resource How Nick Disabato runs two newsletters from one design consultancy Made-for-you share images Automation improvements End-of-email surveys Filter by date Survey-triggered automations More automation functionality New webhooks How France Insider built a news service with paid subscribers Email as primary key How John Willshire unites two businesses in one newsletter Confirmation reminders Email churned subscribers Email-to-draft Subscriber metadata columns ChatGPT integration Faster web archives Referral program Better search results TikTok embeds Subscriber timeline Spotify embeds Improved RSS-to-email Subscribe page OG image New analytics page Google Tag Manager Even more subscriber types Integrating Duda with Buttondown Linktree integration guide Advanced and enterprise plans Framer integration guide API requests page Team collaboration In-email surveys Better CSS settings Better RSS automation fetching! Editor toolbar improvements Smart filters Faster emails page RSS automations Faster email analytics Zapier error codes Image accessibility checks Tags vs newsletters OG image picker Image editor improvements API bulk actions Improved OpenAPI spec Mastodon support Better subscriber filtering Better subscriber validation Hotkey support! Programmatic access to analytics Stronger bulk actions Faster archive page Custom canonical URLs Email slug and metadata Improved writing interface Generating a Typescript router in Django Filter emails by source
Public postmortem: manually adding subscribers failed
Justin Duke · 2026-06-19 · via Buttondown's blog

TL;DR

From around 03:17 UTC on June 14th until around 14:30 UTC on June 15th, manually adding a subscriber through the Buttondown web app failed for many authors with the error "Failed to create this subscriber: extra inputs are not permitted." Importing subscribers and the rest of the app were unaffected; the broken path was specifically the internal "add a subscriber" form. Subscribers attempting to subscribe organically were not affected.

What happened, and why

Buttondown's author-facing dashboard — the app you log into to send emails and manage subscribers — uses Buttondown's external-facing API for the vast majority of its work. As part of a broad initiative to tighten up our API schema — to improve safety for, ironically, exactly things like this, and to make it easier for new users to start working with our API — we've shipped a number of changes you can think of as tightening the language of the API: the functionality doesn't change, but the contract gets stricter about what you give us and what we hand back. One such change, which we rolled out on June 13th (UTC), was to reject as invalid any API request that sends us extraneous data. This is generally considered a good idea, because you don't want to give callers the false impression that anything they pass in is meaningful. Unfortunately, we turned out to be one such caller: our manual subscriber-creation flow passed in an extraneous field — a client-only active toggle — which, whenever it was present, caused the request to fail.

How we discovered it

The tricky thing about this genre of issue is that it also has a common and completely reasonable explanation: someone running an older version of the front-end bundle that talks to our app. If we remove a field from the API contract and deploy that change, a user who hasn't yet refreshed their browser to pick it up can send an API request containing the old field, which then comes back as a 422. We have high-level logging for this specific status code, but it was deliberately insensitive and didn't trip for something like this — a comparatively low-volume, manual path that was still succeeding some of the time.

Sadly, that meant we discovered it through customer support tickets: the worst possible way to find out.

The change was also far too broad to catch by hand. "Reject all undeclared fields, everywhere" can only be fully verified by exercising every workflow that writes to the API — not something you can spot-check, which is how a broken flow reached production unnoticed.

How we mitigated it

On June 15th at 14:04 UTC we merged and deployed a fix that builds the subscriber-create request body from an explicit list of API fields instead of spreading the whole form object, so client-only fields can no longer leak into the payload, along with a regression test that asserts the request body never contains active. App-sourced 422s on the endpoint dropped from roughly 20/hour to near-zero within the half hour, by ~14:30 UTC. In total, 454 author attempts to add a subscriber failed over the window.

A small tail of failures continued through the evening: authors who still had the old, broken bundle loaded in their browser kept hitting the error until their page next reloaded and picked up the fix — the same version-skew effect described above, now working in our favor as the stale bundles aged out.

What we're doing

  1. We've added alerting for app-sourced 422s. A new checker watches first-party (source=app) requests over a settled window and alerts when an endpoint crosses a small threshold of validation failures, grouped by route and annotated with the exact rejected fields (e.g. body.payload.active). A 422 on a first-party request almost always means our own frontend sent a body our own backend rejected — that's now something we catch in hours, not days. Third-party API 422s are intentionally excluded, since those are the caller's responsibility.
  2. Our frontend tests now run against a strict, stateful mock of the API instead of a permissive one that accepted any payload. The whole reason this slipped past code review and CI is that our test doubles were more forgiving than the real backend — the mock happily accepted the active field the real schema rejects. A strict mock closes the gap the type system structurally can't: a test that exercises a form now fails if the form sends a field the API doesn't declare.
  3. We're making the frontend send explicit, whitelisted request bodies rather than spreading form state into payloads, so a client-only field can't silently become an API field. The subscriber form is fixed; we're auditing the remaining write paths for the same pattern.
  4. We're ramping broad, behavior-changing rollouts instead of flipping them globally. Changes like "reject all undeclared fields" alter behavior the OpenAPI contract can't express, so type-safety gives false confidence. Those go out gradually, one endpoint at a time, with 422 rates watched as a guardrail before they reach everyone.

Lastly, we're elevating manual subscriber creation to a core flow, so we invest in testing and rollout work around it the way we already do for things like sending an email or logging in.