
The write that must succeed
On this page
A mailing-list signup looks like the simplest endpoint you will ever write. Take an email, store it, done. It is not simple. It is a small pile of decisions about what is allowed to fail.
I got to make those decisions the honest way, by getting one of them wrong first.
One write that has to land#
The signup writes to an edge key-value store, keyed by the email address itself. Keying by email makes a re-subscribe idempotent: sign up twice and you overwrite the same key, no duplicates, no drama.
That write is the point of the whole endpoint. So it gets to decide the response.
If the store binding is missing, I do not shrug and return a cheerful 200. I return a 503 with an honest error. Silently dropping addresses is the worst outcome here, because it looks like success and loses data. A loud failure is recoverable. A quiet one is not.
The parts that are allowed to fail#
Now the side effects, and this is the bit I initially tangled up.
There is a honeypot field, a hidden input real users never touch. If a bot fills it, the endpoint returns a fake 200 and stores nothing. The bot thinks it won. Nothing happened.
Then there is the “email the owner when someone new signs up” step. My first instinct was to do it inline, before responding. That is a mistake. It means a flaky mail provider can fail or slow down a subscription that already succeeded.
So that step now runs after the response, fire and forget, wrapped so a mail failure is logged and nothing more. The subscriber is already saved. Whether the owner’s notification goes out is not allowed to touch the response code.
Separate the write that must succeed from the nice-to-have side effect, and let only the former decide what you return to the user.
The footgun on shared accounts#
Here is the one that cost me an afternoon and had nothing to do with my logic.
On a shared cloud account, a key-value namespace gets bound to your code by a variable name. The name is a label. It is not the identity of the underlying store.
Which means a binding named the same thing in two projects can quietly point at two different namespaces, or worse, the same one. Everything looks correct. The name matches. The data goes somewhere you did not expect. In practice that can mean one project quietly reading or overwriting another project’s data, with nothing on screen to hint anything is wrong.
The fix is not clever, just paranoid: verify the underlying namespace id, not just the name it is bound under. Names collide silently. Ids do not.
# check the id, not just that a binding called KV existsEvery endpoint has a spine and it has garnish. The spine is the write that must succeed. The garnish is everything you would like to happen but can live without.
Let only the spine affect your status code. Push the garnish after the response and wrap it so it cannot take the request down with it.
And on any shared account, trust ids over names. The name is what you called it. The id is what it actually is.


