@kurtbruns/kestrel 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -1
- package/README.md +19 -27
- package/cli/context.mjs +5 -1
- package/dist/public/dashboard/app.js +367 -256
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +493 -24
- package/dist/worker/index.js +2658 -2065
- package/docs/README.md +4 -5
- package/docs/get-started/02-deploy.md +12 -5
- package/docs/get-started/03-access.md +2 -0
- package/docs/get-started/04-ses.md +57 -17
- package/docs/get-started/05-verify.md +22 -8
- package/docs/get-started/06-go-live.md +13 -53
- package/docs/guides/01-connect-claude.md +7 -3
- package/docs/guides/03-notifications.md +11 -11
- package/docs/guides/09-as-your-list-grows.md +62 -0
- package/docs/publishing/01-your-list.md +157 -0
- package/docs/publishing/02-sending-rules.md +35 -0
- package/docs/publishing/03-name-your-publication.md +20 -0
- package/docs/publishing/04-write-a-post.md +55 -0
- package/docs/publishing/06-troubleshooting.md +74 -0
- package/docs/publishing/07-working-with-claude.md +29 -0
- package/docs/publishing/09-share-your-subscribe-link.md +25 -0
- package/docs/publishing/10-check-your-setup.md +30 -0
- package/docs/publishing/11-email-template.md +34 -0
- package/docs/publishing/README.md +27 -0
- package/docs/reference/01-configuration.md +6 -6
- package/docs/reference/02-sending-domain-dns.md +4 -2
- package/package.json +2 -1
- package/template/claude/CLAUDE.md +5 -2
- package/template/claude/skills/migrate-list/SKILL.md +56 -0
- package/docs/guides/08-try-locally.md +0 -108
- package/docs/reference/03-import-format.md +0 -103
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Self-hosting Kestrel
|
|
2
2
|
|
|
3
|
-
These
|
|
3
|
+
These pages cover deploying and running your own Kestrel, from an empty repository to a live newsletter on your Cloudflare account. Writing and sending the newsletter is covered in [Publishing](publishing/README.md). These pages call whoever writes and sends it the publisher, which may be you.
|
|
4
4
|
|
|
5
5
|
## Get started
|
|
6
6
|
|
|
@@ -17,9 +17,9 @@ Follow these steps, from an empty repository to a live newsletter. Each one ends
|
|
|
17
17
|
Each one stands on its own. Pick the ones you need.
|
|
18
18
|
|
|
19
19
|
- [Connect Claude to the API](guides/01-connect-claude.md)
|
|
20
|
-
- [Try Kestrel locally first](guides/08-try-locally.md)
|
|
21
20
|
- [Notifications through Cloudflare's email](guides/03-notifications.md)
|
|
22
21
|
- [Rate-limit the subscribe form](guides/05-rate-limit.md)
|
|
22
|
+
- [As your list grows](guides/09-as-your-list-grows.md)
|
|
23
23
|
- [Upgrade to a new release](guides/07-upgrade.md)
|
|
24
24
|
|
|
25
25
|
## Reference
|
|
@@ -27,5 +27,4 @@ Each one stands on its own. Pick the ones you need.
|
|
|
27
27
|
The detail behind the steps, for when you need it.
|
|
28
28
|
|
|
29
29
|
- [Configuration](reference/01-configuration.md)
|
|
30
|
-
- [Sending-domain DNS](reference/02-sending-domain-dns.md)
|
|
31
|
-
- [Import format](reference/03-import-format.md)
|
|
30
|
+
- [Sending-domain DNS: SPF, DKIM, and DMARC](reference/02-sending-domain-dns.md)
|
|
@@ -56,7 +56,14 @@ Your instance's repository holds its configuration, its database migrations, and
|
|
|
56
56
|
git push -u origin HEAD
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
1. **(Optional)** Try Kestrel locally before you deploy it.
|
|
59
|
+
1. **(Optional)** Try Kestrel locally before you deploy it. Copy the local settings, and start the app:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cp .dev.vars.example .dev.vars
|
|
63
|
+
npm run dev
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Open http://localhost:8787/dashboard/, and run `npm run seed` in a second terminal to load a demo publication. Email goes to a stand-in, so nothing reaches a real inbox. Stop the server with <kbd>Ctrl</kbd>+<kbd>C</kbd> when you're done.
|
|
60
67
|
|
|
61
68
|
## 2. Sign in to Cloudflare
|
|
62
69
|
|
|
@@ -118,7 +125,7 @@ Name `--env production` on every Wrangler command that creates or binds a resour
|
|
|
118
125
|
}
|
|
119
126
|
],
|
|
120
127
|
"vars": {
|
|
121
|
-
"PROVIDER": "", // ← stays empty until
|
|
128
|
+
"PROVIDER": "", // ← stays empty until Connect Amazon SES
|
|
122
129
|
"APP_ORIGIN": "https://newsletter.example.com", // ← your app's hostname
|
|
123
130
|
"ARCHIVE_BASE_PATH": "/archive",
|
|
124
131
|
"SENDING_DOMAIN": "send.example.com", // ← your sending hostname
|
|
@@ -130,8 +137,8 @@ Name `--env production` on every Wrangler command that creates or binds a resour
|
|
|
130
137
|
What each one does:
|
|
131
138
|
|
|
132
139
|
- **`routes`** puts the app on your hostname when you deploy. Cloudflare creates the DNS record and the certificate. Your domain must be in the same Cloudflare account, and the hostname can't already have a CNAME record; delete one if it does. The block also turns off the `workers.dev` addresses, so the app answers only on your hostname.
|
|
133
|
-
- **`PROVIDER` stays empty.**
|
|
134
|
-
- **`ARCHIVE_BASE_PATH`** is permanent once
|
|
140
|
+
- **`PROVIDER` stays empty.** [Connect Amazon SES](04-ses.md) sets it. Until then the app runs without an email provider: posts can be written and previewed, but scheduling, test emails, and signups are refused. Never set it to `fake`, the top level's value, even when a wrangler warning suggests copying it: `fake` delivers nothing, and the app refuses it on your hostname.
|
|
141
|
+
- **`ARCHIVE_BASE_PATH`** is permanent once the first post goes out. Every post sent carries its `/archive/…` link, and changing the prefix later breaks the links already mailed. Keep `/archive` unless you have a reason not to.
|
|
135
142
|
- **`FROM_ADDRESS`** is the address every email comes from. Keep it on your sending hostname, and set `SENDING_DOMAIN` to the part after the `@`.
|
|
136
143
|
|
|
137
144
|
Change every `example.com` value, the route included, even before you connect a provider. The app checks its settings but can't see the route: it refuses to run while `APP_ORIGIN`, `SENDING_DOMAIN`, or `FROM_ADDRESS`, or `ARCHIVE_ORIGIN` or `MEDIA_PUBLIC_BASE` if you set them, is still on `example.com`, since its links and the address your email comes from would name a domain that isn't yours.
|
|
@@ -195,7 +202,7 @@ This deploys the release your instance runs, with your settings. The output ends
|
|
|
195
202
|
|
|
196
203
|
In a browser, `https://newsletter.example.com/dashboard/` says the editor is locked until you set up Access. That's expected.
|
|
197
204
|
|
|
198
|
-
1. In a browser, `https://newsletter.example.com/` shows
|
|
205
|
+
1. In a browser, `https://newsletter.example.com/` shows the newsletter's public landing page. Its subscribe page, `/subscribe`, says signups aren't open yet, until Connect Amazon SES.
|
|
199
206
|
|
|
200
207
|
If the public pages instead say the newsletter is temporarily unavailable, and `/health` answers `500` with a body like `{"error":"invalid_config","variable":"APP_ORIGIN",…}`, the setting it names is missing, malformed, or still on `example.com`. Fix it in `wrangler.jsonc`, commit, and deploy again.
|
|
201
208
|
|
|
@@ -37,6 +37,8 @@ The editor lives under `/dashboard`, and the API under `/api`. One application c
|
|
|
37
37
|
|
|
38
38
|
Each path covers everything under it. Leave every other path out. The landing page, the archive, subscribing, unsubscribing, images, and your email provider's webhooks must stay public.
|
|
39
39
|
|
|
40
|
+
1. **(Optional)** Choose how long a sign-in lasts. The application's session duration defaults to 24 hours, so everyone signs in again each day. You can set anything from immediately to one month, as Cloudflare's [session management](https://developers.cloudflare.com/cloudflare-one/access-controls/access-settings/session-management/) describes. A longer session suits a publisher on their own computer, and a shorter one is safer on a shared one.
|
|
41
|
+
|
|
40
42
|
## 3. Allow yourself in
|
|
41
43
|
|
|
42
44
|
Every Access application denies everyone until a policy allows them.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Connect Amazon SES
|
|
2
2
|
|
|
3
|
-
Kestrel sends
|
|
3
|
+
Kestrel sends the newsletter's posts through Amazon SES, Amazon's email service. SES only delivers the mail: Kestrel keeps the list, each subscriber's consent, and the record of every send. AWS reviews your account before it lets you send to anyone but yourself. This page asks for that early, and you test in the sandbox while you wait.
|
|
4
4
|
|
|
5
|
-
In this guide, you verify your sending hostname with SES
|
|
5
|
+
In this guide, you verify your sending hostname with SES and add a DMARC record. You forward readers' replies to the publisher's inbox, and ask AWS for production access. You also route bounces and complaints to the app through SNS. Then you give the app its AWS credentials, set the provider, and deploy.
|
|
6
6
|
|
|
7
7
|
## Before you begin
|
|
8
8
|
|
|
@@ -42,7 +42,33 @@ DMARC tells inboxes what to do with mail that fails its checks, and sends you re
|
|
|
42
42
|
|
|
43
43
|
[Sending-domain DNS](../reference/02-sending-domain-dns.md#dmarc-publish-a-policy-and-collect-reports) explains each record, and when to tighten DMARC.
|
|
44
44
|
|
|
45
|
-
## 3.
|
|
45
|
+
## 3. Receive replies at your From address
|
|
46
|
+
|
|
47
|
+
When a reader replies to a post, or to their confirmation email, the reply goes to your From address, `newsletter@send.example.com`. Nothing receives mail there yet, so the reply bounces. Kestrel only sends mail, so you give the address an inbox yourself. Here you forward it to the publisher's inbox, with Cloudflare Email Routing, which is free on every plan. The publisher is whoever writes and sends the newsletter, which may be you.
|
|
48
|
+
|
|
49
|
+
`send.example.com` receives no other mail, so turning on Email Routing there changes nothing else. Your SES records sit on other names under it. Inboxes check SES's mail for SPF at its return path, not here, so sending is unaffected.
|
|
50
|
+
|
|
51
|
+
1. In the [Cloudflare dashboard](https://dash.cloudflare.com/), go to **Compute → Email Service → Email Routing**.
|
|
52
|
+
|
|
53
|
+
1. Add `send.example.com` to Email Routing. Cloudflare adds subdomains from your domain's own settings: select `example.com`, then **Settings**, and add `send` under **Subdomains**.
|
|
54
|
+
|
|
55
|
+
1. Cloudflare adds MX, SPF, and DKIM records for `send.example.com`, and locks them. Wait until they show as added.
|
|
56
|
+
|
|
57
|
+
1. Under **Destination Addresses**, add the publisher's inbox. Open the email Cloudflare sends to it, and select **Verify email address**. If it isn't your inbox, ask the publisher to do this step. An address you've already verified with Cloudflare, such as one for [notifications](../guides/03-notifications.md), is listed already.
|
|
58
|
+
|
|
59
|
+
1. Under **Routing Rules**, select **Create routing rule**. For **Email pattern**, enter `newsletter`, the part of your From address before the `@`, and select `send.example.com`. Set **Action** to **Send to an email**, select the publisher's inbox as the **Destination**, and save.
|
|
60
|
+
|
|
61
|
+
On a subdomain, Email Routing forwards only the addresses you add a rule for, with no catch-all. That's all a From address needs.
|
|
62
|
+
|
|
63
|
+
- **Automatic replies arrive too.** Out-of-office and other automatic replies go to your From address, so they reach the publisher's inbox. Bounces don't: they go to SES's return path, and the app handles them as before.
|
|
64
|
+
- **Your DMARC reports need an inbox too.** Your DMARC record's report address may not receive mail. If so, change the record's `rua` to `mailto:dmarc@send.example.com`, and add a second rule that forwards `dmarc` the same way.
|
|
65
|
+
- **Answers come from the publisher's inbox.** A forwarded reply comes from the reader's own address. An answer to it goes out from the inbox it was forwarded to, not from `newsletter@send.example.com`.
|
|
66
|
+
|
|
67
|
+
To answer from the newsletter's own address, give it a real mailbox instead of forwarding it. Point `send.example.com`'s MX records at a mail host you use, such as [Fastmail](https://www.fastmail.help/hc/en-us/articles/360060591133) or [Google Workspace](https://support.google.com/a/answer/140034). Then create `newsletter@send.example.com` there, as the host's docs describe. If you send from it, the host's SPF and DKIM records go on `send.example.com` beside SES's. SES's mail is unaffected, since its SPF is checked at its return path. And if your From address is on a domain that already receives mail, replies already reach it, and you can skip this section.
|
|
68
|
+
|
|
69
|
+
Some replies ask the publisher to do something, and Kestrel can't see them, so the publisher acts on them, as [Sending rules](../publishing/02-sending-rules.md#when-a-reader-asks-to-leave-or-to-be-deleted) describes.
|
|
70
|
+
|
|
71
|
+
## 4. (Optional) Set a custom MAIL FROM domain
|
|
46
72
|
|
|
47
73
|
SES sends with its own return path by default, so SPF passes for `amazonses.com` rather than for your hostname. DMARC still passes, through DKIM. A custom MAIL FROM domain makes SPF pass for your hostname too, a second check in your favor.
|
|
48
74
|
|
|
@@ -57,11 +83,11 @@ SES sends with its own return path by default, so SPF passes for `amazonses.com`
|
|
|
57
83
|
|
|
58
84
|
1. Wait until SES shows the MAIL FROM domain as **Successful**.
|
|
59
85
|
|
|
60
|
-
##
|
|
86
|
+
## 5. Request production access
|
|
61
87
|
|
|
62
88
|
A new SES account starts in the sandbox. It sends only to addresses you've verified in SES, up to 200 emails a day and one a second. Ask AWS to lift those limits now, and test while you wait.
|
|
63
89
|
|
|
64
|
-
1. Name
|
|
90
|
+
1. Name the publication first, or ask the publisher for its name, since AWS may visit its website to review the request. In the editor, open **Settings → Publication identity**, and enter the name and tagline. Until then, the public pages show the name in your From address. The subscribe form stays closed until section 8 sets the provider.
|
|
65
91
|
|
|
66
92
|
1. In SES, open the **Account dashboard**, select **View Get set up page**, and then **Request production access**.
|
|
67
93
|
|
|
@@ -74,18 +100,18 @@ A new SES account starts in the sandbox. It sends only to addresses you've verif
|
|
|
74
100
|
|
|
75
101
|
1. While you wait, verify the two addresses you test with in [Verify it works](05-verify.md#before-you-begin), so the sandbox lets you mail them. For each, under **Identities**, choose **Create identity**, select **Email address**, and follow the link in the email AWS sends.
|
|
76
102
|
|
|
77
|
-
If AWS asks for more detail, reply in the support case. If it denies the request, reply there, or open a new request from the **Account dashboard**. Either way, say how Kestrel handles
|
|
103
|
+
If AWS asks for more detail, reply in the support case. If it denies the request, reply there, or open a new request from the **Account dashboard**. Either way, say how Kestrel handles the list:
|
|
78
104
|
|
|
79
105
|
- Signups use double opt-in: each subscriber confirms by email before joining the list.
|
|
80
106
|
- Every post offers one-click unsubscribe.
|
|
81
107
|
- Bounces and complaints reach the app through SNS, and it stops mailing those addresses automatically.
|
|
82
|
-
- How many subscribers
|
|
108
|
+
- How many subscribers the newsletter expects, and how often it sends.
|
|
83
109
|
|
|
84
110
|
Your account stays in the sandbox until AWS grants access, so don't go live before then. In the sandbox, a signup from any address you haven't verified fails, and a post reaches only your verified addresses.
|
|
85
111
|
|
|
86
|
-
When AWS grants access, the **Account dashboard** shows your daily quota and your maximum send rate. You set the rate in the app in section
|
|
112
|
+
When AWS grants access, the **Account dashboard** shows your daily quota and your maximum send rate. You set the rate in the app in section 8. The rate decides how fast a send goes out, and [As your list grows](../guides/09-as-your-list-grows.md) says when to ask AWS to raise it.
|
|
87
113
|
|
|
88
|
-
##
|
|
114
|
+
## 6. Send bounces and complaints to SNS
|
|
89
115
|
|
|
90
116
|
SES reports what happens to each email through SNS, the AWS notification service. A configuration set says which events to report, and an SNS topic carries them to the app.
|
|
91
117
|
|
|
@@ -120,9 +146,9 @@ SES reports what happens to each email through SNS, the AWS notification service
|
|
|
120
146
|
|
|
121
147
|
Replace the topic ARN, and both copies of the account ID, with the values from your ARN. Replace `us-east-1` with your region's code, the same one the ARN shows. Choose **Save changes**.
|
|
122
148
|
|
|
123
|
-
You subscribe the app to the topic in section
|
|
149
|
+
You subscribe the app to the topic in section 9, once it runs on SES.
|
|
124
150
|
|
|
125
|
-
##
|
|
151
|
+
## 7. Create the app's AWS key
|
|
126
152
|
|
|
127
153
|
The app sends through an IAM user whose key can only send email. That limits the harm if the key ever leaks.
|
|
128
154
|
|
|
@@ -155,7 +181,7 @@ The app sends through an IAM user whose key can only send email. That limits the
|
|
|
155
181
|
|
|
156
182
|
1. Copy the access key ID and the secret access key. AWS shows the secret only once.
|
|
157
183
|
|
|
158
|
-
##
|
|
184
|
+
## 8. Set the provider and deploy
|
|
159
185
|
|
|
160
186
|
Until now the app has run with no email provider connected, refusing to send. Setting `PROVIDER` connects SES. Store the secrets before you set it. With `PROVIDER` set to `ses` and a required secret missing, the app refuses to run, and sends nothing, until you store it. `/health` and the editor's **A setting needs fixing** page name the missing secret, and reader pages say the newsletter is temporarily unavailable. `SES_CONFIGURATION_SET` is the one optional secret.
|
|
161
187
|
|
|
@@ -171,7 +197,7 @@ Until now the app has run with no email provider connected, refusing to send. Se
|
|
|
171
197
|
npx wrangler secret put AWS_SECRET_ACCESS_KEY --env production
|
|
172
198
|
```
|
|
173
199
|
|
|
174
|
-
1. Store the topic ARN from section
|
|
200
|
+
1. Store the topic ARN from section 6. The app accepts events only from this topic:
|
|
175
201
|
|
|
176
202
|
```bash
|
|
177
203
|
npx wrangler secret put SNS_TOPIC_ARN --env production
|
|
@@ -197,7 +223,7 @@ Until now the app has run with no email provider connected, refusing to send. Se
|
|
|
197
223
|
}
|
|
198
224
|
```
|
|
199
225
|
|
|
200
|
-
On Workers Paid, also add `"SUBREQUEST_BUDGET": "10000"`, the plan's limit, so each minute sends more. Leave it out on Workers Free. [As your list grows](
|
|
226
|
+
On Workers Paid, also add `"SUBREQUEST_BUDGET": "10000"`, the plan's limit, so each minute sends more. Leave it out on Workers Free. [As your list grows](../guides/09-as-your-list-grows.md) says when the plan is worth it.
|
|
201
227
|
|
|
202
228
|
When AWS grants production access, set `SES_MAX_SEND_RATE` to the maximum send rate on the **Account dashboard**, and deploy again. The app keeps to that rate, so SES doesn't throttle your send.
|
|
203
229
|
|
|
@@ -214,7 +240,7 @@ Until now the app has run with no email provider connected, refusing to send. Se
|
|
|
214
240
|
npx wrangler deploy --env production
|
|
215
241
|
```
|
|
216
242
|
|
|
217
|
-
##
|
|
243
|
+
## 9. Subscribe the app to the topic
|
|
218
244
|
|
|
219
245
|
SNS delivers events only to a subscriber that has confirmed. The app confirms on its own, once it checks that the request came from your topic.
|
|
220
246
|
|
|
@@ -234,7 +260,7 @@ If it stays **Pending confirmation**, the request never reached the app, or the
|
|
|
234
260
|
|
|
235
261
|
- **Access covers `/webhooks/ses`.** If your Access application covers more than `/dashboard` and `/api`, SNS gets the login page instead of the app. Remove that path from your Access application, so only `dashboard` and `api` remain.
|
|
236
262
|
- **`SNS_TOPIC_ARN` matches the topic's ARN.**
|
|
237
|
-
- **The deploy in section
|
|
263
|
+
- **The deploy in section 8 finished.**
|
|
238
264
|
|
|
239
265
|
Once you've fixed the cause, select the subscription, and choose **Request confirmation**. SNS sends the request again, and the app confirms it. SNS doesn't let you delete a subscription while it's pending, and removes it on its own after 48 hours.
|
|
240
266
|
|
|
@@ -272,11 +298,25 @@ Kestrel can't tell whether those emails went out. SES's console shows sent messa
|
|
|
272
298
|
|
|
273
299
|
1. The SNS subscription shows **Confirmed**.
|
|
274
300
|
|
|
301
|
+
1. Your From address has somewhere to go. In Cloudflare, your domain's DNS records show the ones Email Routing added for `send`, beside section 1's DKIM records:
|
|
302
|
+
|
|
303
|
+
- **Type:** `MX`, **Name:** `send`, three of them, for `route1.mx.cloudflare.net`, `route2.mx.cloudflare.net`, and `route3.mx.cloudflare.net`
|
|
304
|
+
- **Type:** `TXT`, **Name:** `send`, **Content:** `v=spf1 include:_spf.mx.cloudflare.net ~all`
|
|
305
|
+
- **Type:** `TXT`, **Name:** `cf2024-1._domainkey.send`, Cloudflare's DKIM key
|
|
306
|
+
|
|
307
|
+
The world sees the MX records too:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
dig +short MX send.example.com
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
It lists the three `route` servers. Email Routing's **Routing Rules** shows your rule for `newsletter@send.example.com`, and **Destination Addresses** shows the publisher's inbox as verified. A reply to your test email in the next step proves the forwarding works.
|
|
314
|
+
|
|
275
315
|
1. In the editor, **Settings → Email sender** shows Amazon SES and your From address, where it said **No email provider connected** before.
|
|
276
316
|
|
|
277
317
|
1. In a browser, `https://newsletter.example.com/subscribe` shows the subscribe form, where it said signups aren't open yet.
|
|
278
318
|
|
|
279
|
-
The test sends in the next step prove the rest. In the sandbox, send them only to the addresses you verified in section
|
|
319
|
+
The test sends in the next step prove the rest. In the sandbox, send them only to the addresses you verified in section 5, and to the SES test addresses that page lists.
|
|
280
320
|
|
|
281
321
|
SES charges for each email it sends, test emails included. [Amazon SES pricing](https://aws.amazon.com/ses/pricing/) lists the rates.
|
|
282
322
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Verify it works
|
|
2
2
|
|
|
3
|
-
Some things only show up with real email: whether inboxes trust your mail, how it looks in a real mail client, and whether bounces and unsubscribes come back to the app. Check them now, while
|
|
3
|
+
Some things only show up with real email: whether inboxes trust your mail, how it looks in a real mail client, and whether bounces and unsubscribes come back to the app. Check them now, while the list is empty and every email goes only to you.
|
|
4
4
|
|
|
5
|
-
In this guide, you send yourself a test email and check that it passes authentication. Then you set up
|
|
5
|
+
In this guide, you send yourself a test email, reply to it, and check that it passes authentication. Then you set up notifications, send a real post to yourself as a subscriber, and unsubscribe. Finally, you check that bounces and complaints reach the app, read the app's logs, and check that SES can send to anyone.
|
|
6
6
|
|
|
7
7
|
## Before you begin
|
|
8
8
|
|
|
9
|
-
- Two email addresses you own, such as a personal address and a work address. Some steps use both. While your SES account is in the sandbox, verify both in SES first, as [Connect Amazon SES](04-ses.md#
|
|
9
|
+
- Two email addresses you own, such as a personal address and a work address. Some steps use both. While your SES account is in the sandbox, verify both in SES first, as [Connect Amazon SES](04-ses.md#5-request-production-access) describes.
|
|
10
10
|
- A Gmail address, or another mail client that shows a message's original source and an **Unsubscribe** button.
|
|
11
11
|
|
|
12
12
|
## 1. Send yourself a test email
|
|
@@ -19,6 +19,8 @@ A test email goes through the same code as a real send. If the test looks right,
|
|
|
19
19
|
|
|
20
20
|
1. Open the email. It should arrive in your inbox, not in spam. The image loads and the subject reads as you wrote it. Its **View in browser** link opens "Nothing here yet" until the post goes out, and its unsubscribe link opens the page a subscriber sees, marked as a test and showing a sample address. Its button walks through to the confirmation, but a test email unsubscribes no one. Section 5 unsubscribes for real.
|
|
21
21
|
|
|
22
|
+
1. Reply to it, as a reader would. Reply from an address other than the inbox your From address forwards to, such as your second address. Gmail doesn't show you a message you sent when it comes back to you. Send that address a test email too if you need one. The reply arrives in the inbox you chose in [Connect Amazon SES](04-ses.md#3-receive-replies-at-your-from-address). The app can't see your forwarding, so this is the only proof that readers can reach the publisher. If the reply bounces, check the routing rule and the destination address there.
|
|
23
|
+
|
|
22
24
|
## 2. Check that it passes authentication
|
|
23
25
|
|
|
24
26
|
Inboxes trust mail that passes DKIM and DMARC for the domain it's from. Passing both is the best single sign that your mail reaches the inbox.
|
|
@@ -35,9 +37,9 @@ Inboxes trust mail that passes DKIM and DMARC for the domain it's from. Passing
|
|
|
35
37
|
|
|
36
38
|
If a check fails, compare your DNS records with the ones in [Connect Amazon SES](04-ses.md#1-verify-your-sending-hostname). [Sending-domain DNS](../reference/02-sending-domain-dns.md#verify-alignment) explains each result.
|
|
37
39
|
|
|
38
|
-
## 3. Set up
|
|
40
|
+
## 3. Set up notifications
|
|
39
41
|
|
|
40
|
-
Kestrel emails
|
|
42
|
+
Kestrel emails the notifications address when a send finishes, and right away when one runs into a problem. Notifications go through your email provider. To send them through Cloudflare instead, see [Notifications through Cloudflare's email](../guides/03-notifications.md).
|
|
41
43
|
|
|
42
44
|
1. In the editor, open **Settings → Notifications**.
|
|
43
45
|
|
|
@@ -45,6 +47,8 @@ Kestrel emails you when a send finishes, and right away when one runs into a pro
|
|
|
45
47
|
|
|
46
48
|
1. Select **Send a test notification**. It arrives at that address.
|
|
47
49
|
|
|
50
|
+
Use your own address to test. If you're not the publisher, they set theirs in [Check your setup](../publishing/10-check-your-setup.md).
|
|
51
|
+
|
|
48
52
|
## 4. Send a real post to yourself
|
|
49
53
|
|
|
50
54
|
This is a real send, to a list of one. It proves the whole path: subscribing, confirming, scheduling, and sending.
|
|
@@ -53,7 +57,7 @@ This is a real send, to a list of one. It proves the whole path: subscribing, co
|
|
|
53
57
|
|
|
54
58
|
1. Open the confirmation email, follow its link, and select **Confirm**. In the editor, **Subscribers** shows the address as **Confirmed**.
|
|
55
59
|
|
|
56
|
-
1. Open
|
|
60
|
+
1. Open the post, select **Schedule**, and then **Send now**. Every send waits at least five minutes before it goes out, so you can cancel a mistake.
|
|
57
61
|
|
|
58
62
|
1. Cancel the send, to see that canceling stops it. Then select **Send now** again, and let it go.
|
|
59
63
|
|
|
@@ -80,11 +84,11 @@ When an address bounces or marks your mail as spam, SES tells the app through SN
|
|
|
80
84
|
|
|
81
85
|
1. In the editor, **Subscribers** lists both addresses as **Pending**, each with a **Suppressed** flag. The app never mails a suppressed address again.
|
|
82
86
|
|
|
83
|
-
If an address has no flag, SES's events aren't reaching the app. Check the configuration set and the SNS subscription in [Connect Amazon SES](04-ses.md#
|
|
87
|
+
If an address has no flag, SES's events aren't reaching the app. Check the configuration set and the SNS subscription in [Connect Amazon SES](04-ses.md#6-send-bounces-and-complaints-to-sns).
|
|
84
88
|
|
|
85
89
|
## 7. Read the logs
|
|
86
90
|
|
|
87
|
-
The app writes a log line for each thing it does, and Cloudflare keeps them for a few days. Nothing else tells you if the once-a-minute schedule that sends
|
|
91
|
+
The app writes a log line for each thing it does, and Cloudflare keeps them for a few days. Nothing else tells you if the once-a-minute schedule that sends posts stops, so it's worth knowing where to look.
|
|
88
92
|
|
|
89
93
|
1. In the Cloudflare dashboard, go to **Workers & Pages**, select `kestrel-production`, and select **Observability**.
|
|
90
94
|
|
|
@@ -94,6 +98,16 @@ The app writes a log line for each thing it does, and Cloudflare keeps them for
|
|
|
94
98
|
|
|
95
99
|
1. Filter on `level` equal to `error`. A healthy instance shows nothing.
|
|
96
100
|
|
|
101
|
+
## 8. Check that SES can send to anyone
|
|
102
|
+
|
|
103
|
+
[Connect Amazon SES](04-ses.md#5-request-production-access) asked AWS for production access. Until AWS grants it, the sandbox mails only your verified addresses, so check it before you go live.
|
|
104
|
+
|
|
105
|
+
1. In SES, open the **Account dashboard** in the region the app sends from. It no longer says your account is in the sandbox, and it shows your daily quota and maximum send rate.
|
|
106
|
+
|
|
107
|
+
1. Check that `SES_MAX_SEND_RATE` in `wrangler.jsonc` matches that rate, and that you deployed after setting it, as [Connect Amazon SES](04-ses.md#8-set-the-provider-and-deploy) describes.
|
|
108
|
+
|
|
109
|
+
If the dashboard still says sandbox, AWS hasn't answered yet. Every other section works in the sandbox, so come back to this one when it does.
|
|
110
|
+
|
|
97
111
|
When every section gives the result it describes, your instance is ready for subscribers.
|
|
98
112
|
|
|
99
113
|
Next, [go live](06-go-live.md).
|
|
@@ -1,65 +1,25 @@
|
|
|
1
1
|
# Go live
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
At this point, your instance should be deployed, verified, and out of the SES sandbox, so it can send to anyone. From here, the publisher can set up the publication, and you can continue with the guides below.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Start publishing
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The rest happens in the editor, at `https://newsletter.example.com/dashboard/`. The Publishing docs walk through it in the order of the dashboard's setup checklist, starting with [Name your publication](../publishing/03-name-your-publication.md). The checklist itself shows only until the first post exists, so the test post from [Verify it works](05-verify.md) has already hidden it.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- `SES_MAX_SEND_RATE` set to the maximum send rate on the SES **Account dashboard**, and deployed. [Connect Amazon SES](04-ses.md#7-set-the-provider-and-deploy) sets it to `1` for the sandbox.
|
|
9
|
+
If someone else is the publisher, let them sign in first:
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
1. Add their email address to the `Publishers` policy on the Access application, as in [Allow yourself in](03-access.md#3-allow-yourself-in). If you set `ACCESS_ALLOWED_EMAILS`, store the list again with their address added.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
1. If they don't have a Cloudflare account, add another way to sign in, such as a one-time code by email. It's under **Zero Trust → Integrations → Identity providers**.
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
1. Send them the editor's address and a link to [Name your publication](../publishing/03-name-your-publication.md).
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
## Optional next steps
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
1. **(Optional)** Select **Add a mailing address**. US law asks for a postal address in promotional email, and a P.O. box works. The built-in email template prints it in every email's footer.
|
|
23
|
-
|
|
24
|
-
## 2. Share your subscribe link
|
|
25
|
-
|
|
26
|
-
Readers subscribe on a page the app hosts. Every signup confirms by email before it joins your list.
|
|
27
|
-
|
|
28
|
-
1. In the editor, open **Settings → Ways to subscribe**.
|
|
29
|
-
|
|
30
|
-
1. Copy your subscribe page's link, `https://newsletter.example.com/subscribe`, and share it.
|
|
31
|
-
|
|
32
|
-
1. **(Optional)** To take signups on your own website, copy the embeddable subscribe form from the same page, and paste it into your site.
|
|
33
|
-
|
|
34
|
-
1. **(Optional)** Cap how often one visitor can submit the form. The app already sends any one address at most one confirmation every 15 minutes. A Cloudflare rule can also limit each visitor, and the Free plan includes one. See [Rate-limit the subscribe form](../guides/05-rate-limit.md).
|
|
35
|
-
|
|
36
|
-
## As your list grows
|
|
37
|
-
|
|
38
|
-
A new instance runs on Cloudflare's free Workers plan. As your list grows, three limits come into play.
|
|
39
|
-
|
|
40
|
-
- **Your SES quota.** AWS caps how many emails your account sends a day. A send that hits the cap waits, and the app emails you. It resumes on its own once the quota frees up, or after AWS raises it.
|
|
41
|
-
- **How fast a send goes out.** A send goes out a slice at a time, one slice a minute. SES takes one request a second, of up to 50 recipients, and no more recipients a second than your account's maximum send rate. So each request carries one second of that rate, up to 50.
|
|
42
|
-
- **Your database's daily writes.** On the free plan, D1, the database the app runs on, allows 100,000 rows written a day. Past that, it refuses every query until midnight UTC, and the whole app stops: sends, signups, the public pages, and the editor.
|
|
43
|
-
|
|
44
|
-
It's recommended to move to the $5 Workers Paid plan from about 5,000 subscribers. A send writes about 14 database rows a recipient, delivery receipts included. So one send uses up the free plan's day at about 7,000 recipients, and sooner when an import or anything else writes that day. Workers Paid counts [D1's writes](https://developers.cloudflare.com/d1/platform/pricing/) by the month instead, with 50 million included, and never stops the database for them. On the free plan, the app warns you before a send to more than 5,000 recipients, in the Schedule dialog and on the scheduled post. It never stops you, since your account may already be on Workers Paid.
|
|
45
|
-
|
|
46
|
-
The free Workers plan sends four requests a minute. At 14 messages a second, a common starting rate, that's 56 recipients a minute, and 1,000 subscribers take about 18 minutes. At 50 a second or more, it's 200 a minute.
|
|
47
|
-
|
|
48
|
-
To send faster, first ask AWS to raise your maximum send rate to 50, which costs nothing, then set [`SES_MAX_SEND_RATE`](../reference/01-configuration.md#ses_max_send_rate) to match, and deploy again. A rate above 50 adds nothing on the free plan, where a rate of 50 sends 5,000 subscribers in about 25 minutes. A larger list belongs on Workers Paid for its database writes anyway. On Workers Paid, set [`SUBREQUEST_BUDGET`](../reference/01-configuration.md#subrequest_budget) to `10000`. It sends about 50 requests a minute: about 700 recipients at 14 a second, and about 2,500 at 50 or more.
|
|
49
|
-
|
|
50
|
-
## Check it
|
|
51
|
-
|
|
52
|
-
1. The SES **Account dashboard**, in the region the app sends from, no longer says your account is in the sandbox.
|
|
53
|
-
|
|
54
|
-
1. `https://newsletter.example.com/subscribe` shows your publication's name and logo.
|
|
55
|
-
|
|
56
|
-
1. `https://newsletter.example.com/` shows your publication's name, and the post you sent yourself in [Verify it works](05-verify.md#4-send-a-real-post-to-yourself).
|
|
57
|
-
|
|
58
|
-
## What's next
|
|
59
|
-
|
|
60
|
-
Each of these guides stands on its own. Pick the ones you want.
|
|
19
|
+
Each of these guides adds one thing, and you can come back to any of them later.
|
|
61
20
|
|
|
62
21
|
- [Connect Claude to the API](../guides/01-connect-claude.md), so Claude can draft, proofread, and schedule posts through the same API the editor uses.
|
|
63
|
-
- [Notifications through Cloudflare's email](../guides/03-notifications.md), so
|
|
64
|
-
|
|
65
|
-
|
|
22
|
+
- [Notifications through Cloudflare's email](../guides/03-notifications.md), so problems are reported even when the email provider has stopped sending.
|
|
23
|
+
- [Rate-limit the subscribe form](../guides/05-rate-limit.md), before the subscribe link is shared widely.
|
|
24
|
+
- [As your list grows](../guides/09-as-your-list-grows.md), from about 5,000 subscribers.
|
|
25
|
+
- [Upgrade to a new release](../guides/07-upgrade.md), when one comes out.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Connect Claude to the API
|
|
2
2
|
|
|
3
|
-
Claude Code can draft, edit, proofread, and schedule
|
|
3
|
+
Claude Code can draft, edit, proofread, and schedule posts through the same API the editor uses. It has its own Cloudflare Access credential, so its changes show as Claude's, not a person's. Anything it schedules waits out the review window, where the publisher can still cancel it.
|
|
4
4
|
|
|
5
5
|
In this guide, you create a service token for Claude Code, and add a policy for it to your Access application. Then you store the token where Claude Code reads it, check that it works, and point Claude at the API reference.
|
|
6
6
|
|
|
@@ -75,7 +75,11 @@ CF-Access-Client-Secret headers from your environment with every request.
|
|
|
75
75
|
Read GET /api/reference first, and use it for every call.
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
The reference is also served as Markdown, `GET /api/reference.md`, which reads more easily for Claude than the JSON. So are the docs: `GET /api/docs/publishing.md` lists the publisher's pages, `GET /api/docs/self-hosting.md` this guide's, and each page is at `GET /api/docs/<slug>.md`.
|
|
79
|
+
|
|
80
|
+
Claude reads the reference, and from there can list the posts, write a draft, and schedule it. The reference lists the body types each route takes. Most take JSON, sent with `Content-Type: application/json`. An image upload takes a multipart form, or the image file itself.
|
|
81
|
+
|
|
82
|
+
To bring subscribers over from another service, ask Claude to import the list, as [Bring your existing list](../publishing/01-your-list.md) describes. Its [file format](../publishing/01-your-list.md#the-file-format) section describes every column.
|
|
79
83
|
|
|
80
84
|
## Check it
|
|
81
85
|
|
|
@@ -85,7 +89,7 @@ Claude reads the reference, and from there can list your posts, write a draft, a
|
|
|
85
89
|
{"principal":{"kind":"service"},"auth":{"mode":"access"}}
|
|
86
90
|
```
|
|
87
91
|
|
|
88
|
-
1. Ask Claude to list
|
|
92
|
+
1. Ask Claude to list the posts. It answers with the posts the editor shows.
|
|
89
93
|
|
|
90
94
|
1. Open a draft in the editor, and ask Claude to change a word in it. The editor shows that the draft was changed elsewhere, last edited by **Claude**.
|
|
91
95
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Notifications through Cloudflare's email
|
|
2
2
|
|
|
3
|
-
Kestrel emails
|
|
3
|
+
Kestrel emails the publisher when a send finishes, and right away when one runs into a problem. By default, these notifications go through the email provider. The one that matters most, about the provider refusing your account, is the one the provider would refuse too. Cloudflare's email doesn't depend on the provider, so that notification still reaches the publisher.
|
|
4
4
|
|
|
5
|
-
In this guide, you turn on Cloudflare's email for your app's hostname, and verify
|
|
5
|
+
In this guide, you turn on Cloudflare's email for your app's hostname, and verify the notifications address with it. Then you add the binding that sends notifications through it, deploy, and send yourself a test.
|
|
6
6
|
|
|
7
7
|
## Before you begin
|
|
8
8
|
|
|
9
9
|
You need:
|
|
10
10
|
|
|
11
|
-
-
|
|
11
|
+
- The notifications address saved, from [Verify it works](../get-started/05-verify.md#3-set-up-notifications).
|
|
12
12
|
- Your domain's DNS on Cloudflare, as for the rest of the app.
|
|
13
13
|
|
|
14
14
|
## 1. Turn on Email Routing for your app's hostname
|
|
@@ -21,15 +21,15 @@ Cloudflare's email sends only from a domain set up with Email Routing. Use your
|
|
|
21
21
|
|
|
22
22
|
1. Cloudflare adds MX, SPF, and DKIM records for `newsletter.example.com`, and locks them. Wait until they show as added.
|
|
23
23
|
|
|
24
|
-
Avoid turning on Email Routing for a domain that already receives mail elsewhere, such as `example.com` with Google Workspace. Email Routing takes over that domain's MX records.
|
|
24
|
+
Avoid turning on Email Routing for a domain that already receives mail elsewhere, such as `example.com` with Google Workspace. Email Routing takes over that domain's MX records. Don't send notifications from `send.example.com` either, since your email provider sends from it. Email Routing there to forward readers' replies, as [Connect Amazon SES](../get-started/04-ses.md#3-receive-replies-at-your-from-address) sets up, is fine, since it only receives.
|
|
25
25
|
|
|
26
|
-
## 2. Verify
|
|
26
|
+
## 2. Verify the notifications address
|
|
27
27
|
|
|
28
28
|
Cloudflare's email delivers only to addresses you've verified with it. Sending to a verified address is free on any plan.
|
|
29
29
|
|
|
30
30
|
1. Under **Email Routing**, go to **Destination Addresses**, and add the address you saved for notifications.
|
|
31
31
|
|
|
32
|
-
1. Open the email Cloudflare sends to that address, and select **Verify email address**.
|
|
32
|
+
1. Open the email Cloudflare sends to that address, and select **Verify email address**. If it's the publisher's address, ask them to do this step.
|
|
33
33
|
|
|
34
34
|
A verified address belongs to your Cloudflare account, so every environment in it can use it.
|
|
35
35
|
|
|
@@ -41,11 +41,11 @@ A `send_email` binding named `NOTIFY` moves notifications to Cloudflare's email.
|
|
|
41
41
|
|
|
42
42
|
```jsonc
|
|
43
43
|
"send_email": [
|
|
44
|
-
{ "name": "NOTIFY", "allowed_destination_addresses": ["you@example.com"] } // ←
|
|
44
|
+
{ "name": "NOTIFY", "allowed_destination_addresses": ["you@example.com"] } // ← the notifications address
|
|
45
45
|
],
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
`allowed_destination_addresses` is optional. It limits the binding to the addresses you list, so a changed setting in the editor can't send notifications anywhere else. Leave it out to allow any address you've verified.
|
|
48
|
+
`allowed_destination_addresses` is optional. It limits the binding to the addresses you list, so a changed setting in the editor can't send notifications anywhere else. Leave it out to allow any address you've verified. If the publisher changes the notifications address later, verify the new one and add it here too.
|
|
49
49
|
|
|
50
50
|
1. **(Optional)** Notifications come from `Kestrel <kestrel@newsletter.example.com>`. To use another address on that hostname, set `NOTIFY_FROM` in the block's `vars`:
|
|
51
51
|
|
|
@@ -76,13 +76,13 @@ A `send_email` binding named `NOTIFY` moves notifications to Cloudflare's email.
|
|
|
76
76
|
|
|
77
77
|
1. In the editor, open **Settings → Notifications**. **Sent through** reads "Cloudflare Email, separate from your newsletter's provider", and **From address** shows `kestrel@newsletter.example.com`.
|
|
78
78
|
|
|
79
|
-
1. Select **Send a test notification**. It arrives at
|
|
79
|
+
1. Select **Send a test notification**. It arrives at the notifications address, and **Last notification** shows it as delivered.
|
|
80
80
|
|
|
81
|
-
If the test isn't delivered, **Last notification** shows Cloudflare's reason, such as an address that isn't verified. Cloudflare's Email Routing summary lists notifications as dropped even when they're delivered, so check
|
|
81
|
+
If the test isn't delivered, **Last notification** shows Cloudflare's reason, such as an address that isn't verified. Cloudflare's Email Routing summary lists notifications as dropped even when they're delivered, so check the notifications inbox rather than the summary.
|
|
82
82
|
|
|
83
83
|
## What notifications don't cover
|
|
84
84
|
|
|
85
|
-
- **The schedule stopping.** The same once-a-minute schedule that sends
|
|
85
|
+
- **The schedule stopping.** The same once-a-minute schedule that sends posts also notices problems. If the schedule itself stops, nothing tells you. [Read the logs](../get-started/05-verify.md#7-read-the-logs) to check that `sweep.tick` still arrives every minute.
|
|
86
86
|
- **A notification that can't get through.** The app tries each one up to five times, a minute apart. A failure shows under **Settings → Notifications**, and in the logs as `notify.failed`.
|
|
87
87
|
- **Problems while no address is set.** With the address blank, the app sends nothing, and saves nothing up for later.
|
|
88
88
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# As your list grows
|
|
2
|
+
|
|
3
|
+
A new instance runs on Cloudflare's free Workers plan with your starting Amazon SES limits. That suits a list of up to about 5,000 subscribers. Past that, two limits matter: how fast SES lets you send, and how many database writes the free plan allows a day.
|
|
4
|
+
|
|
5
|
+
In this guide, you raise your SES limits, which helps on either plan, and upgrade to Workers Paid, which a larger list needs. Then you see how long a send takes after each.
|
|
6
|
+
|
|
7
|
+
## 1. Raise your SES limits
|
|
8
|
+
|
|
9
|
+
SES caps how many emails your account sends a day, and how many a second. A send that hits the daily quota waits, emails you, and resumes on its own once the quota frees up. Raising your maximum send rate to 50 a second costs nothing, and it makes every send faster.
|
|
10
|
+
|
|
11
|
+
1. Ask AWS for a higher daily quota and a maximum send rate of 50, as AWS's [Increasing your Amazon SES sending quotas](https://docs.aws.amazon.com/ses/latest/dg/manage-sending-quotas-request-increase.html) describes.
|
|
12
|
+
|
|
13
|
+
1. When AWS grants it, the SES **Account dashboard** shows your new rate. In `wrangler.jsonc`, set the `production` block's [`SES_MAX_SEND_RATE`](../reference/01-configuration.md#ses_max_send_rate) to match.
|
|
14
|
+
|
|
15
|
+
1. Commit the change, and push it:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git commit -am "Match the SES send rate"
|
|
19
|
+
git push
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
1. Deploy:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx wrangler deploy --env production
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 2. Upgrade to Workers Paid
|
|
29
|
+
|
|
30
|
+
On the free plan, [D1](https://developers.cloudflare.com/d1/platform/pricing/) allows a fixed number of database writes a day. A send to about 7,000 recipients uses them all, and then the whole app stops until midnight UTC. Until you upgrade, the app warns the publisher before a send or an import that could use up the day. It's recommended to upgrade from about 5,000 subscribers.
|
|
31
|
+
|
|
32
|
+
1. Upgrade your Cloudflare account to the $5 [Workers Paid plan](https://developers.cloudflare.com/workers/platform/pricing/), which has no daily limit on writes.
|
|
33
|
+
|
|
34
|
+
1. In `wrangler.jsonc`, add `"SUBREQUEST_BUDGET": "10000"` to the `production` block's `vars`. It tells the app it's on Workers Paid, so each minute sends far more ([`SUBREQUEST_BUDGET`](../reference/01-configuration.md#subrequest_budget)).
|
|
35
|
+
|
|
36
|
+
1. Commit the change, and push it:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git commit -am "Run on Workers Paid"
|
|
40
|
+
git push
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
1. Deploy:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx wrangler deploy --env production
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## How long a send takes
|
|
50
|
+
|
|
51
|
+
| Setup | 1,000 subscribers | 5,000 subscribers | 10,000 subscribers |
|
|
52
|
+
| --- | --- | --- | --- |
|
|
53
|
+
| Free plan, a starting SES rate of 14 | about 18 minutes | about 1.5 hours | over the free plan's daily writes |
|
|
54
|
+
| Free plan, SES rate raised to 50 | about 5 minutes | about 25 minutes | over the free plan's daily writes |
|
|
55
|
+
| Workers Paid, SES rate of 14 | about 2 minutes | about 7 minutes | about 15 minutes |
|
|
56
|
+
| Workers Paid, SES rate of 50 | under a minute | about 2 minutes | about 4 minutes |
|
|
57
|
+
|
|
58
|
+
## Check it
|
|
59
|
+
|
|
60
|
+
1. The SES **Account dashboard** shows the maximum send rate you set in `SES_MAX_SEND_RATE`.
|
|
61
|
+
|
|
62
|
+
1. On Workers Paid, the import screen under **Subscribers → Import** reads "CSV or .txt, up to 10,000 rows."
|