# Why Always Risk On sends its email through Buttondown behind an adapter

Canonical: <https://alwaysriskon.com/writeups/choosing-the-email-list>
Published: 2026-09-30

Always Risk On sends its email through Buttondown, the simpler of the two providers for launch, and the Worker reaches it through a contract that Beehiiv also meets. Switching takes a setting, Beehiiv's API key and publication id, a Beehiiv account and the list moved across.

## Key numbers

| Claim                                 | Value      | Measured on                                   | As of      |
| ------------------------------------- | ---------- | --------------------------------------------- | ---------- |
| Provider adapters behind one contract | 2 adapters | the subscribe Worker's source, September 2026 | 2026-09-30 |

## The situation

Always Risk On is a publication, and its job is to grow an audience of people who build and operate things and like numbers. Email is how that audience is kept, because a reader who gives you an address can be reached again and a reader who only visited cannot. Buttondown or Beehiiv had to be chosen before launch, since the subscribe form sits in the hero of the home page and has to work on the first day.

The decision was which provider sends the email, and how hard it would be to change my mind later.

## The numbers

The decision turned on what each provider gave at launch and what it would cost to switch.

*The two providers against what launch needed.*

| Provider   | Double opt-in | Referrals | Adapter written |
| ---------- | ------------- | --------- | --------------- |
| Buttondown | yes           | no        | yes             |
| Beehiiv    | yes           | yes       | yes             |

Both confirm a new address by email before it counts, which the site needs, because a list padded with addresses nobody confirmed is worse than a short one. Beehiiv has a referral programme built in, and the referral column is the reason it was in the running at all. Both adapters got written, against the same contract and the same tests.

Lines of source in each email adapter, from the subscribe Worker, 50 for Buttondown and 68 for Beehiiv. [Figure](https://alwaysriskon.com/writeups/choosing-the-email-list#fig1)

The adapters are small. Buttondown's is 50 lines of source and Beehiiv's is 68, and the difference is mostly Beehiiv mapping its own subscription states onto the contract's.

## The options

The first option was Buttondown now. It does one job, sending email to a list, with double opt-in and an API that takes a new subscriber in one request. It had nothing to set up that launch didn't need.

The second was Beehiiv now. It is a bigger product with a referral programme built in, and it is good at what it does, which is exactly why its adapter exists. But referrals only pay once there is a list worth referring from, and on launch day there isn't one.

The third was the obvious shortcut, a pop-up that asks for the address before the reader has read anything. It would probably collect more addresses than a quiet form in the hero. It would also be a pop-up, so it was never built.

## The call

Buttondown, behind an adapter. The Worker never talks to Buttondown directly. It talks to a contract with a call to subscribe an address and a call to read the confirmed count, and each provider is a file that meets it. The same contract tests run against both adapters on recorded replies, so the Beehiiv adapter already passes them before anyone needs it. It has never called Beehiiv's live API.

*Pick the simple sender now and keep the swap to one file.*

## What happened

The form posts to a Worker on the same domain, and the Worker does the checking before any provider sees an address. It checks the origin and caps the size of the request. A hidden field that only a bot fills in gets a fake success and nothing else. Each network address is limited to 5 requests in 60 seconds, and the limit covers a whole block of neighbouring addresses where one machine can hold many, so rotating through them doesn't get round it.

The provider call runs behind an 8 second timeout, so a slow provider gets a clean failure message and the form comes back ready to try again. With JavaScript off the form still works, because the Worker answers a plain form post with a redirect to a page that says what happened.

**50 lines against 295**

- Buttondown adapter: 50
- Subscribe Worker route: 295

That is where the lines went. The Buttondown adapter is 50 lines and the Worker route around it is 295, so swapping providers means replacing the small part and keeping the part that does the work.

Unit tests on each part of the subscribe path, from vitest's own count, the Worker route carrying the most. [Figure](https://alwaysriskon.com/writeups/choosing-the-email-list#fig2)

The tests follow the same shape. The Worker route has the most, then the adapter contract, then the address check and the form script.

## The cost

The hours and the spend on the list are my own figures, not measured ones, and they sit on the positions page and not here. The Beehiiv adapter is 68 lines of source that nothing runs until referrals are wanted, and that is the price of keeping the switch small.

## What carries forward

The contract carries forward. Any provider that can subscribe an address and report a confirmed count fits behind it, and the contract tests check it against recorded replies before it goes live. In the code, the provider setting plus Beehiiv's API key and publication id are the only change. Outside it, the switch needs a Beehiiv account, the list moved across and the confirmation email set up again.

What it leaves open is referrals. The list has to be worth referring from before Beehiiv's programme earns the switch, and until then the Beehiiv adapter stays written, tested and switched off.

## Check it yourself

- **The adapters**: 2 adapters, Subscribe on the home page. The confirmation email comes from Buttondown, the adapter in use.

## Sources

1. [Buttondown API reference](https://docs.buttondown.com/api-reference/introduction)
2. [Cloudflare Workers rate limiting](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/)
