@kurtbruns/kestrel 1.18.0 → 1.19.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.
@@ -209,7 +209,7 @@ npx wrangler delete --name newsletter-dev
209
209
 
210
210
  In a browser, `https://newsletter.example.com/dashboard/` says the editor is locked until you set up Access. That's expected.
211
211
 
212
- 1. In a browser, `https://newsletter.example.com/` shows the newsletter's public home page. Its subscribe page, `/subscribe`, says signups aren't open yet, until Connect Amazon SES.
212
+ 1. In a browser, `https://newsletter.example.com/` shows the newsletter's public home page. Its subscribe page, `/subscribe`, says signups aren't open yet, until Connect Amazon SES has sent its first test email.
213
213
 
214
214
  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.
215
215
 
@@ -79,13 +79,21 @@ The app verifies each login against your team domain, and checks that it was iss
79
79
 
80
80
  A secret takes effect as soon as you store it, with no redeploy.
81
81
 
82
- 1. **(Optional, recommended)** Limit the editor to named people. The Access policy decides who can sign in. This list keeps that true if the policy is widened later, for another app or by mistake. Enter a comma-separated list of email addresses at the prompt:
82
+ 1. **(Optional, recommended)** Limit the editor to named people. The Access policy decides who can sign in. This list keeps that true if the policy is widened later, for another app or by mistake. Store the list of addresses allowed in:
83
83
 
84
84
  ```bash
85
85
  npx wrangler secret put ACCESS_ALLOWED_EMAILS --env production
86
86
  ```
87
87
 
88
- Without it, the app lets in anyone your Access policy allows.
88
+ At the prompt, enter the addresses separated by commas:
89
+
90
+ ```
91
+ you@example.com, publisher@example.com
92
+ ```
93
+
94
+ Spaces and capital letters don't matter. Without the list, the app lets in anyone your Access policy allows.
95
+
96
+ To let someone else into the editor, such as the publisher or a co-editor, add their email address to the policy from [Allow yourself in](#3-allow-yourself-in). If you set `ACCESS_ALLOWED_EMAILS`, run the command again and enter the whole list with their address added, since the new value replaces the old one.
89
97
 
90
98
  ## Check it
91
99
 
@@ -112,7 +120,7 @@ The app verifies each login against your team domain, and checks that it was iss
112
120
  If a check fails:
113
121
 
114
122
  - **The editor says the configuration needs a fix.** Store the secret it names. While only one of the two is stored, `/health` answers `500` naming the other, and the public pages say the newsletter is temporarily unavailable.
115
- - **Cloudflare lets you in, but the editor answers `401`.** Check the two secrets for a typo, and that `ACCESS_ALLOWED_EMAILS`, if you set it, includes your address.
123
+ - **Cloudflare lets you in, but the editor answers `401`.** Check the two secrets for a typo, and that `ACCESS_ALLOWED_EMAILS`, if you set it, includes your address. The same goes for anyone else you let in.
116
124
  - **The editor says Access let you in but didn't say who you are.** Your login carried no email, which happens through a kind of policy the app doesn't accept, such as a device check. Sign out, and sign in through the policy you made above.
117
125
 
118
126
  Next, [create an AWS account](04-aws-account.md).
@@ -19,7 +19,7 @@ The same form sets a custom MAIL FROM domain, so SPF also passes for your domain
19
19
 
20
20
  SES keeps identities, quotas, and the sandbox separately in each region, so do every step on this page in the region you chose. [Configuration](../reference/01-configuration.md#vars) says more about choosing one, under `AWS_REGION`.
21
21
 
22
- 1. Note the region's code, such as `eu-west-1`. The menu shows it beside the region's name, such as Europe (Ireland), and [Amazon SES endpoints](https://docs.aws.amazon.com/general/latest/gr/ses.html) lists each one too. Later steps use this code.
22
+ 1. Note the region's code, such as `us-west-2`. The menu shows it beside the region's name, and [Amazon SES endpoints](https://docs.aws.amazon.com/general/latest/gr/ses.html) lists each one too. AWS names a region in more than one way, such as United States (Oregon) and US West (Oregon), so go by the code. Later steps use it.
23
23
 
24
24
  1. Under **Configuration**, choose **Identities**, then **Create identity**.
25
25
 
@@ -73,27 +73,31 @@ A new SES account starts in the sandbox: it sends only to addresses you've verif
73
73
 
74
74
  1. Choose two addresses you own, such as a personal address and a work address. Verify it works uses both.
75
75
 
76
- 1. For each, under **Identities**, choose **Create identity**, select **Email address**, and enter it. Choose **Create identity**.
76
+ 1. For each, under **Identities**, choose **Create identity**, select **Email address**, and enter it. Leave **Assign a default configuration set** and **Assign to a tenant** unchecked. The form's notice about DMARC is about sending from the address, and the app only sends to it. Choose **Create identity**.
77
77
 
78
78
  1. Open the email AWS sends to each address, and follow its link. The identity's status changes to **Verified**.
79
79
 
80
+ AWS's email and confirmation page say you can now send from the address. That's expected: SES has one kind of verified address, and the same verification lets the sandbox send to it.
81
+
80
82
  ## 4. Send bounces and complaints to the app
81
83
 
82
84
  SES reports bounces and complaints through Amazon Simple Notification Service (SNS), which delivers them to the app. The app then stops mailing an address that bounced or complained. A configuration set says which events SES reports, and an SNS topic carries them.
83
85
 
84
- 1. In SES, under **Configuration**, choose **Configuration sets**, then **Create set**. Name it `kestrel-mail`, and choose **Create set**.
86
+ 1. In SES, under **Configuration**, choose **Configuration sets**, then **Create set**. Name it `kestrel-mail`, keep every other option as it is, and choose **Create set**.
85
87
 
86
88
  1. On the set's **Event destinations** tab, choose **Add destination**. Select **Hard bounces**, **Complaints**, **Deliveries**, and **Rendering failures**, and choose **Next**. The app logs a rendering failure, an email SES couldn't build, as an error.
87
89
 
88
- 1. For **Destination type**, select **Amazon SNS**. Name it `kestrel-sns`, and keep **Event publishing** enabled. Choose **Next**.
90
+ Leave the other six off, since the app already knows about them or doesn't act on them. **Sends** repeats what the send records, and **Delivery delays** end as a bounce or a delivery. **Rejects** (a virus) and **Subscriptions** (SES's own unsubscribe) don't arise, since the app's emails carry no attachments and their own unsubscribe link. **Opens** and **Clicks** would add tracking the app doesn't record.
91
+
92
+ 1. For **Destination type**, select **Amazon SNS**. Name it `kestrel-sns`, and keep **Event publishing** enabled.
89
93
 
90
- 1. For **SNS topic**, choose **Create SNS topic**. Make it a **Standard** topic named `kestrel-events`. Choose **Next**, then **Add destination**.
94
+ 1. On the same screen, under **SNS topic**, choose **Create SNS topic**. Name it `kestrel-events`, leave the display name empty, and choose **Create topic**. Then select `kestrel-events` in the topic menu, if it isn't already. Choose **Next**, then **Add destination**.
91
95
 
92
- 1. Make the set your hostname's default, so SES reports on every email sent from it. Open the `send.example.com` identity, and choose the **Configuration set** tab. Under **Default configuration set**, choose **Edit**, select `kestrel-mail`, and save.
96
+ 1. Make the set your hostname's default, so SES reports on every email sent from it. Under **Identities**, open `send.example.com`, and choose its **Configuration set** tab, below the summary. Edit the **Default configuration set**, select **Assign a default configuration set**, select `kestrel-mail`, and save.
93
97
 
94
98
  1. Open the topic in the [SNS console](https://console.aws.amazon.com/sns/), in the same region, and find its ARN. It looks like `arn:aws:sns:us-east-1:111122223333:kestrel-events`, where `us-east-1` is your region's code and `111122223333` is your account ID.
95
99
 
96
- 1. Let SES publish to the topic. Choose **Edit**, expand **Access policy**, and add this statement to the `Statement` list:
100
+ 1. Check that SES may publish to the topic, on its **Access policy** tab. A topic created from SES's **Add destination**, as above, already lets SES publish to it from your account. Leave that statement as it is. For a topic you made in the SNS console instead, choose **Edit**, expand **Access policy**, and add this statement to the `Statement` list:
97
101
 
98
102
  ```json
99
103
  {
@@ -126,15 +130,13 @@ SES reports bounces and complaints through Amazon Simple Notification Service (S
126
130
 
127
131
  ## 5. Create the app's AWS key
128
132
 
129
- The app sends through an AWS Identity and Access Management (IAM) user whose key can only send email and read the account's sending status. That limits the harm if the key ever leaks. Reading the status lets the app show whether SES is still in the sandbox, take its send rate from the account, and warn when bounces and complaints can't reach it.
133
+ The app sends through an AWS Identity and Access Management (IAM) user whose key can only send email and read the account's sending status. That limits the harm if the key ever leaks. Reading the status lets the app show whether SES is still in the sandbox and take its send rate from the account. It also warns, without sending anything, when the configuration set is missing or paused, the sender isn't verified, or bounces and complaints can't reach the app.
130
134
 
131
- If `kestrel-sender` already exists, as on an instance set up before Kestrel read the account, don't create a second user or key. In the IAM console, open **Policies**, then `kestrel-send-email`, and choose **Edit**. In the JSON editor, add `"ses:GetAccount"` and `"ses:GetEmailIdentity"` to its `Action` list, as in the policy below, and save. The app's next check, within an hour, reads the account.
135
+ If `kestrel-sender` already exists, from an earlier release, don't create a second user or key. Update its policy instead, as the end of this section shows.
132
136
 
133
- 1. In the [IAM console](https://console.aws.amazon.com/iam/), go to **Users**, and choose **Create user**. Name it `kestrel-sender`, and leave console access off.
137
+ 1. In the [IAM console](https://console.aws.amazon.com/iam/), choose **Policies**, then **Create policy**.
134
138
 
135
- 1. When asked for permissions, choose **Attach policies directly**, then **Create policy**. The policy editor opens in a new browser tab.
136
-
137
- 1. In the new tab, choose the **JSON** editor, and replace its contents with this policy:
139
+ 1. Choose the **JSON** editor, and replace its contents with this policy:
138
140
 
139
141
  ```json
140
142
  {
@@ -145,8 +147,11 @@ If `kestrel-sender` already exists, as on an instance set up before Kestrel read
145
147
  "Action": [
146
148
  "ses:SendEmail",
147
149
  "ses:SendBulkEmail",
150
+ "ses:SendTemplatedEmail",
151
+ "ses:SendBulkTemplatedEmail",
148
152
  "ses:GetAccount",
149
- "ses:GetEmailIdentity"
153
+ "ses:GetEmailIdentity",
154
+ "ses:GetConfigurationSet"
150
155
  ],
151
156
  "Resource": "*"
152
157
  }
@@ -154,13 +159,17 @@ If `kestrel-sender` already exists, as on an instance set up before Kestrel read
154
159
  }
155
160
  ```
156
161
 
157
- Choose **Next**, name the policy `kestrel-send-email`, and choose **Create policy**. You can close this tab.
162
+ 1. Choose **Next**, name the policy `kestrel-send-email`, and choose **Create policy**. The review lists Pinpoint Email, SES, and SES v2, which is how IAM shows these SES actions.
163
+
164
+ 1. Choose **IAM users**, then **Create user**. Name it `kestrel-sender`, leave console access off, and choose **Next**.
158
165
 
159
- 1. Go back to the first tab, where you're creating the user. Refresh the policy list, search for `kestrel-send-email`, and select it.
166
+ 1. Choose **Attach policies directly**, search for `kestrel-send-email`, and select it. AWS recommends groups, which help manage many people, and one app's user doesn't need one. Choose **Next**, then **Create user**.
160
167
 
161
- 1. Choose **Next**, then **Create user**.
168
+ 1. Open `kestrel-sender`, and choose **Create access key**. It's an access key, not an API key. For the use case, choose **Application running outside AWS**, and choose **Next**.
162
169
 
163
- 1. Open the user, and on the **Security credentials** tab, choose **Create access key**. For the use case, choose **Application running outside AWS**.
170
+ AWS suggests IAM Roles Anywhere instead. It needs a certificate authority of your own, so use the access key.
171
+
172
+ 1. For the description tag, enter the app's hostname, such as `newsletter.example.com`, so you can tell its key apart later. Choose **Create access key**.
164
173
 
165
174
  1. Keep the page open: AWS shows the secret access key only once. Store the access key ID in the app, and paste it at the prompt:
166
175
 
@@ -174,6 +183,8 @@ If `kestrel-sender` already exists, as on an instance set up before Kestrel read
174
183
  npx wrangler secret put AWS_SECRET_ACCESS_KEY --env production
175
184
  ```
176
185
 
186
+ To update an existing key's policy, open **Policies** in the IAM console, then `kestrel-send-email`, and choose **Edit**. In the JSON editor, make its `Action` list match the policy above, and save. Without `ses:SendTemplatedEmail`, SES refuses even a test email. Once the key may call `ses:GetAccount`, the app's next check, within an hour, reads the account. Without `ses:GetConfigurationSet`, the app can't tell whether the configuration set exists, so it says nothing about it rather than guessing.
187
+
177
188
  ## 6. Connect the app and deploy
178
189
 
179
190
  Until now the app has run with no email provider connected, refusing to send. Setting `PROVIDER` to `ses` connects SES, with the secrets you stored in sections 4 and 5. If a required one is missing, the app refuses to run until you store it, and `/health` names it.
@@ -222,6 +233,8 @@ Until now the app has run with no email provider connected, refusing to send. Se
222
233
 
223
234
  1. In the editor, **Settings → Email sender** shows Amazon SES and your From address, where it said **No email provider connected** before. Within a couple of minutes, a **Sandbox** pill appears beside it, once the app has read your SES account.
224
235
 
236
+ 1. In the editor, open **Template** and select **Send test email**, to an address you verified in [Verify your test addresses](#3-verify-your-test-addresses). It arrives. Signups stay closed until SES takes an email, so the **Not proven yet** pill beside Amazon SES in **Settings → Email sender** is gone once it has. If SES refuses it, the test's line says why and what fixes it.
237
+
225
238
  1. In a browser, `https://newsletter.example.com/subscribe` shows the subscribe form, where it said signups aren't open yet.
226
239
 
227
240
  ## 7. Subscribe the app to the topic
@@ -240,7 +253,7 @@ SNS delivers events only to a subscriber that has confirmed. The app confirms on
240
253
 
241
254
  Any other answer, such as a redirect to your Cloudflare login, means Access covers the webhook. Remove that path from your Access application.
242
255
 
243
- 1. In the SNS console, open your topic, and choose **Create subscription**.
256
+ 1. In the SNS console, open your topic, and choose **Create subscription**. Check that **Topic ARN** shows the `kestrel-events` topic, and choose it if it's empty.
244
257
 
245
258
  1. Set **Protocol** to **HTTPS**, and enter the endpoint:
246
259
 
@@ -248,13 +261,9 @@ SNS delivers events only to a subscriber that has confirmed. The app confirms on
248
261
  https://newsletter.example.com/webhooks/ses
249
262
  ```
250
263
 
251
- 1. Leave raw message delivery off, and choose **Create subscription**.
252
-
253
- 1. Reload the subscription. Its status changes to **Confirmed**, with nothing for you to click.
254
-
255
- If it stays **Pending confirmation**, check that your Access application covers only `/dashboard` and `/api`, that `SNS_TOPIC_ARN` matches the topic's ARN, and that section 6's deploy finished. A mismatched ARN shows in the app's logs as a `webhook.refused` error naming both. Then request confirmation again from the topic's subscriptions. SNS doesn't let you delete a pending subscription, and removes it on its own after 48 hours.
264
+ 1. Leave raw message delivery off.
256
265
 
257
- 1. Give SNS longer to retry. By default it retries a failed delivery 3 times over about a minute, then discards the event, so a bounce that arrives mid-deploy is lost. Open the topic, choose **Edit**, and expand **Delivery retry policy (HTTP/S)**. Turn off the default policy, set these values, and choose **Save changes**:
266
+ 1. Give SNS longer to retry. By default it retries a failed delivery 3 times over about a minute, then discards the event, so a bounce that arrives mid-deploy is lost. Expand **Delivery policy (HTTP/S)**, turn off **Use the default delivery policy**, and set these values:
258
267
 
259
268
  | Field | Value |
260
269
  | --- | --- |
@@ -266,13 +275,19 @@ SNS delivers events only to a subscriber that has confirmed. The app confirms on
266
275
  | Maximum delay retries | `6` |
267
276
  | Retry-backoff function | Exponential |
268
277
 
269
- SNS then keeps trying for about 40 minutes. It allows an hour at most.
278
+ SNS then keeps trying for about 40 minutes. It allows an hour at most. The topic has a delivery policy of its own, under **Edit**. Setting it there instead also covers any subscription you add later.
279
+
280
+ 1. Choose **Create subscription**.
281
+
282
+ 1. Reload the subscription. Its status changes to **Confirmed**, with nothing for you to click.
283
+
284
+ If it stays **Pending confirmation**, check that your Access application covers only `/dashboard` and `/api`. Check that `SNS_TOPIC_ARN` matches the topic's ARN, and that section 6's deploy finished. A mismatched ARN shows in the app's logs as a `webhook.refused` error naming both. Then request confirmation again from the topic's subscriptions. SNS doesn't let you delete a pending subscription, and removes it on its own after 48 hours.
270
285
 
271
286
  1. **(Optional)** Keep what SNS still can't deliver. On the subscription, choose **Edit**, and set a **Redrive policy (dead-letter queue)** to an SQS queue of your own. SNS then moves an event there instead of discarding it. [Amazon SNS dead-letter queues](https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html) covers the queue and its permissions.
272
287
 
273
288
  ## 8. Receive replies at your From address
274
289
 
275
- Replies to a post go to your From address, `newsletter@send.example.com`, which has no inbox yet, so they bounce. This section forwards that address to the publisher's inbox with Cloudflare Email Routing. Forwarding only receives mail, so it doesn't change how SES sends.
290
+ Replies to a post go to your From address, `newsletter@send.example.com`, which has no inbox yet, so they bounce. The reader is told your domain couldn't be found. This section forwards that address to the publisher's inbox with Cloudflare Email Routing. Forwarding only receives mail, so it doesn't change how SES sends.
276
291
 
277
292
  1. Check that your From address has no inbox yet. This prints nothing:
278
293
 
@@ -286,7 +301,7 @@ Replies to a post go to your From address, `newsletter@send.example.com`, which
286
301
 
287
302
  1. Add `send.example.com` to Email Routing: select `example.com`, then **Settings**, and add `send` under **Subdomains**.
288
303
 
289
- 1. Cloudflare adds MX, SPF, and DKIM records for `send.example.com`, and locks them. Wait until they show as added.
304
+ 1. Cloudflare adds MX, SPF, and DKIM records for `send.example.com`, and locks them. Wait until they show as added. Its SPF record is for forwarding: SES's mail passes SPF at `bounce.send.example.com`, from section 1.
290
305
 
291
306
  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.
292
307
 
@@ -300,6 +315,10 @@ Replies to a post go to your From address, `newsletter@send.example.com`, which
300
315
  dig +short MX send.example.com
301
316
  ```
302
317
 
318
+ 1. Send an email to `newsletter@send.example.com` from an address whose mail doesn't go through the publisher's inbox. Gmail, for one, hides a forwarded copy of mail you sent yourself. In Email Routing's **Activity log**, the email shows as **Forwarded**.
319
+
320
+ If it bounces saying the domain couldn't be found, the sender's provider is still remembering that `send.example.com` had no mail servers. That happens after an email to it before this section, and lasts up to 30 minutes on Cloudflare's DNS. Wait, and send it again.
321
+
303
322
  Out-of-office and other automatic replies reach the publisher's inbox too. Bounces don't: they go to SES, which reports them to the app. The publisher answers from their own inbox, not from `newsletter@send.example.com`. To answer from the newsletter's address instead, give it a mailbox at a mail host such as [Fastmail](https://www.fastmail.help/hc/en-us/articles/360060591133) or [Google Workspace](https://support.google.com/a/answer/140034), in place of forwarding.
304
323
 
305
324
  SES is now connected in the sandbox. Until AWS grants production access, it sends only to the addresses you verified in section 3. It also sends to the SES test addresses Verify it works uses. SES charges for each email it sends, test emails included, as [Amazon SES pricing](https://aws.amazon.com/ses/pricing/) lists.
@@ -34,11 +34,13 @@ AWS reviews each request by hand, for the region the app sends from.
34
34
 
35
35
  1. Check the acknowledgement, and select **Submit request**. AWS Support answers by email.
36
36
 
37
- 1. Within about ten minutes, the editor's **Settings → Email sender** says AWS is reviewing your request for production access. It also says when AWS grants or denies it.
37
+ 1. Within about ten minutes, the editor's **Settings → Email sender** says AWS is reviewing your request for production access, and later whether AWS has granted it.
38
+
39
+ Within seconds of every request, AWS sends an automated email asking for more information, in a support case. The SES console then shows **More information needed**, and the editor says AWS hasn't granted production access yet and has replied in AWS Support, with the case number. That's expected, and not a refusal: answer it as the next section describes.
38
40
 
39
41
  ## 3. Answer AWS's questions
40
42
 
41
- AWS Support may ask how you collect addresses and handle bounces before it decides. A newsletter may be asked for more detail, or denied the first time, sometimes without a reason. A reply that explains how the list is built usually gets further than a new request. If it asks for more detail, or denies the request, reply in the support case, or open a new request from the **Account dashboard**.
43
+ AWS asks how you collect addresses and handle bounces before it decides, every time. A newsletter may be asked for more detail again, or turned down the first time, sometimes without a reason. A reply that explains how the list is built usually gets further than a new request. Reply in the support case, whichever it is, or open a new request from the **Account dashboard**. The editor reads the same until AWS grants access, since AWS's account reports a request for more information and a refusal alike.
42
44
 
43
45
  In the sandbox, the subscribe form's confirmation email reaches only addresses verified in SES. If AWS's reviewer tries the form, say that this is why no confirmation arrives. In your reply, cover:
44
46
 
@@ -48,6 +50,7 @@ In the sandbox, the subscribe form's confirmation email reaches only addresses v
48
50
  - That the publication's website shows what the newsletter is, and how readers subscribe.
49
51
  - That an imported list was gathered with opt-in on the previous service.
50
52
  - How many subscribers the newsletter expects, and how often it sends.
53
+ - A sample email. Send yourself a test of a post, as [Verify it works](07-verify.md#1-send-yourself-a-test-email) shows, and attach it to your reply or paste its text.
51
54
 
52
55
  ## 4. Check that AWS granted it
53
56
 
@@ -55,7 +58,7 @@ Until AWS grants production access, SES sends only to your verified addresses, s
55
58
 
56
59
  1. In the editor, open **Settings → Email sender**. The email provider reads **Production**. From the API, `npx kestrel api GET /api/provider` shows `state` as `production`.
57
60
 
58
- 1. If it reads **Sandbox**, AWS hasn't granted access yet. Meanwhile, continue with [Verify it works](07-verify.md), which runs in the sandbox. Come back to this section when AWS answers, and check the email AWS Support sends for questions.
61
+ 1. If it reads **Sandbox**, AWS hasn't granted access yet. If it also says AWS has replied, with a case number, answer in that case, as section 3 describes. Meanwhile, continue with [Verify it works](07-verify.md), which runs in the sandbox. Come back to this section when AWS answers.
59
62
 
60
63
  1. If it says Kestrel's AWS key needs permission to read your account, select **Add the permission** and follow the step it opens. The app checks again within an hour.
61
64
 
@@ -41,9 +41,9 @@ If a check fails, compare your DNS records with the ones in [Connect Amazon SES]
41
41
 
42
42
  ## 3. Check that notifications arrive
43
43
 
44
- Kestrel emails a notifications address when a send finishes, and right away when one runs into a problem. Check that they reach you, using one of the addresses you verified in SES.
44
+ Kestrel emails each notifications address when a send finishes, and right away when one runs into a problem. Check that they reach you, using one of the addresses you verified in SES. While SES is in the sandbox, every notifications address has to be verified there too, unless notifications go through [Cloudflare's email](../guides/03-notifications.md), where each is verified with Cloudflare instead. If you're setting Kestrel up for someone else, you can add their address beside yours later.
45
45
 
46
- 1. In the editor, open **Settings → Notifications**, enter that address, and select **Save**.
46
+ 1. In the editor, open **Settings → Notifications**, type that address under **Send notifications to**, select **Add inbox**, and then **Save**.
47
47
 
48
48
  1. Select **Send a test notification**. It arrives at that address.
49
49
 
@@ -8,10 +8,12 @@ The rest happens in the editor, at `https://newsletter.example.com/dashboard/`.
8
8
 
9
9
  If someone else is the publisher, let them sign in first:
10
10
 
11
- 1. Add their email address to the policy on the Access application that lets you in, 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.
11
+ 1. Add their email address to the policy on the Access application that lets you in, and to `ACCESS_ALLOWED_EMAILS` if you set it, as in [Give the app its two values](03-access.md#4-give-the-app-its-two-values).
12
12
 
13
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**.
14
14
 
15
+ 1. Add their address under **Settings → Notifications** beside yours, so a problem reaches both of you.
16
+
15
17
  1. Send them the editor's address and a link to [Name your publication](../publishing/03-name-your-publication.md).
16
18
 
17
19
  ## Optional next steps
@@ -43,7 +43,7 @@ Managed OAuth lets an app such as Claude act with a person's account, the way th
43
43
  curl -si https://newsletter.example.com/api/whoami
44
44
  ```
45
45
 
46
- It answers `401`, and its `www-authenticate` line starts with `Bearer` and names `resource_metadata`. Without Managed OAuth, Cloudflare answers with a redirect to its login page instead. A browser still gets the login page either way.
46
+ You should see `401`, with a `www-authenticate` line that starts with `Bearer`. Access can take a few minutes more to start accepting sign-ins. Until it does, signing in shows `invalid_target`; wait, then try again.
47
47
 
48
48
  ## 2. Connect Claude, or send the publisher the address
49
49
 
@@ -2,13 +2,13 @@
2
2
 
3
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 the notifications address with it. Then you add the binding that sends notifications through it, deploy, and send yourself a test.
5
+ In this guide, you turn on Cloudflare's email for your app's hostname, and verify the notifications addresses 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
- - The notifications address saved, from [Verify it works](../get-started/07-verify.md#3-check-that-notifications-arrive).
11
+ - The notifications addresses saved, from [Verify it works](../get-started/07-verify.md#3-check-that-notifications-arrive).
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
@@ -23,13 +23,13 @@ Cloudflare's email sends only from a domain set up with Email Routing. Use your
23
23
 
24
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/05-ses.md#8-receive-replies-at-your-from-address) sets up, is fine, since it only receives.
25
25
 
26
- ## 2. Verify the notifications address
26
+ ## 2. Verify the notifications addresses
27
27
 
28
- Cloudflare's email delivers only to addresses you've verified with it. Sending to a verified address is free on any plan.
28
+ Cloudflare's email delivers only to addresses you've verified with it. Sending to a verified address is free on any plan. Notifications can go to several addresses, such as the publisher's and yours, and each one needs verifying.
29
29
 
30
- 1. Under **Email Routing**, go to **Destination Addresses**, and add the address you saved for notifications.
30
+ 1. Under **Email Routing**, go to **Destination Addresses**, and add each address saved for notifications.
31
31
 
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.
32
+ 1. Open the email Cloudflare sends to each 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"] } // ← the notifications address
44
+ { "name": "NOTIFY", "allowed_destination_addresses": ["you@example.com"] } // ← the notifications addresses
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. If the publisher changes the notifications address later, verify the new one and add it here too.
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 adds or changes a 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,9 +76,9 @@ 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 <kestrel@newsletter.example.com>`.
78
78
 
79
- 1. Select **Send a test notification**. It arrives at the notifications address, and **Last notification** shows it as delivered.
79
+ 1. Select **Send a test notification**. It arrives at each 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 the notifications inbox rather than the summary.
81
+ If the test isn't delivered to an address, **Last notification** shows Cloudflare's reason, such as an address that isn't verified, after the address when there are several. The other addresses still get it. 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
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  When something needs you, Kestrel says so in a line on the send's page, under **Sent**. The top of the dashboard shows it too. Red needs you to act, and amber is worth a look. The line says what happened, what to do, and who does it. This page covers what the screen can't: the fixes made outside Kestrel, and the decisions that are yours.
4
4
 
5
- **Set a notifications address, so a problem reaches you without your watching.** Set it under **Settings → Notifications**, in **Send notifications to**. With none set, Kestrel emails you nothing. [Notifications](06-troubleshooting.md#notifications), below, lists what you're told.
5
+ **Set a notifications address, so a problem reaches you without your watching.** Set it under **Settings → Notifications**, in **Send notifications to**: type an address and choose **Add inbox**. Add the address of whoever runs your instance too, and both of you are told. With none set, Kestrel emails you nothing. [Notifications](06-troubleshooting.md#notifications), below, lists what you're told.
6
6
 
7
7
  ## A send is held or waiting
8
8
 
@@ -33,6 +33,27 @@ When none come, the send's page says so an hour after it went out. Your provider
33
33
 
34
34
  While Amazon SES is in its sandbox, it mails only addresses verified in SES. Verify the address you test with in the SES console, or [request production access](../get-started/06-production-access.md). When the test's line says the provider didn't answer, check the inbox before you send it again, since the first may have arrived. A test SES refuses for your account has the same fix as [a send it refuses](06-troubleshooting.md#a-send-is-held-or-waiting).
35
35
 
36
+ ## Signups aren't open yet, or are paused
37
+
38
+ Your subscribe page takes signups only while email is working, so a reader is never told a link is coming when none can go. Every visitor gets the same page, whatever address they enter.
39
+
40
+ - "Signups aren’t open yet" means SES hasn't sent an email for your publication yet. Send one from **Template → Send test email**, to an address verified in SES while it's in the sandbox. Once SES takes it, signups open. Before an email provider is connected, the page says the same, and [Connect Amazon SES](../get-started/05-ses.md) is the fix.
41
+ - "Signups are paused" means SES is refusing your email, every email including confirmations and tests. The dashboard's **Email isn’t going out** line gives SES's words and the fix, which whoever runs your instance makes, as for [a problem with Amazon SES](#a-problem-with-amazon-ses). Then send a test email: once SES takes one, signups reopen.
42
+
43
+ Kestrel notices a refusal when an email meets it, or within about ten minutes from its check of your SES account. A refusal the check found lifts on its own within about ten minutes of the fix; one an email met lifts when a test email goes through. A reader who signed up just as it started may have been told a link was coming, and can sign up again once signups reopen.
44
+
45
+ ## A problem with Amazon SES
46
+
47
+ **Settings → Email sender** and the top of the dashboard name a problem with your Amazon SES account within about ten minutes, without sending anything. Red means no email can go out until it's fixed, tests and confirmations included. Amber is worth a look. Each line links the step that fixes it, and whoever runs your instance makes the fix:
48
+
49
+ - **The configuration set is missing, or its sending is off.** The set Kestrel sends with, the one `SES_CONFIGURATION_SET` names or the sending identity's default if that secret isn't set, isn't in your region, or its sending is turned off. Correct the name, or create the set, as [Connect Amazon SES](../get-started/05-ses.md#4-send-bounces-and-complaints-to-the-app) shows. Turn a set's sending back on in the SES console, under its configuration sets.
50
+ - **SES rejects the AWS key.** The key ID is unknown, the secret doesn't match it, or the AWS account isn't signed up for SES. Store the key's ID and secret again, or create a new key, as [Connect Amazon SES](../get-started/05-ses.md#5-create-the-apps-aws-key) shows.
51
+ - **The sender isn't verified.** SES has no verified identity for the From address in your region. Verify the sending hostname, as [Connect Amazon SES](../get-started/05-ses.md#1-verify-your-sending-hostname) shows.
52
+ - **Sending is turned off for the account.** AWS documents turning it back on only with the AWS CLI, naming your region: `aws sesv2 put-account-sending-attributes --sending-enabled --region us-east-1`.
53
+ - **AWS paused the account, or is reviewing it.** AWS does this when the account's bounce or complaint rate runs high. While it reviews, SES still sends, and the line is amber. Once it pauses sending, the line is red. Either way, AWS's email to the AWS account says what it needs: answer it in AWS Support, and check the addresses on your recent sends' records.
54
+
55
+ The app needs to read each of these. If its key isn't allowed to, the line says nothing rather than guessing. The policy in [Connect Amazon SES](../get-started/05-ses.md#5-create-the-apps-aws-key) allows all of it.
56
+
36
57
  ## Your list outgrows Workers Free
37
58
 
38
59
  Upgrade the Cloudflare account to Workers Paid, and set `SUBREQUEST_BUDGET` so Kestrel sends faster, as [As your list grows](../guides/09-as-your-list-grows.md) describes. Kestrel warns you before a send that could use up the free plan's daily database writes, which stops the app until midnight UTC. [Bring your existing list](01-your-list.md#limits) explains the limits.
@@ -60,8 +81,10 @@ Nothing on your list changes. Kestrel refuses a file it can't read with certaint
60
81
  Most failures are in your instance's Cloudflare Access setup, made by whoever runs your instance. [Connect Claude](08-connect-claude.md) covers connecting it. When that fails:
61
82
 
62
83
  - **Claude says it couldn't connect, or the sign-in failed.** Your instance's Access application needs Managed OAuth set up as [Turn on Managed OAuth](../guides/01-allow-claude.md#1-turn-on-managed-oauth) describes, including Claude's sign-in address for Claude Desktop and claude.ai. Check that the connector's address is your publication's address followed by `/api/mcp`, and that your instance is deployed, not running on your own computer.
84
+ - Signing in shows `invalid_target` (`OAuth not enabled for the targeted resource`). Access can take a few minutes to start accepting sign-ins once Managed OAuth is on. Wait, then try again.
63
85
  - `kestrel login` says your instance "doesn't offer a sign-in for programs". Managed OAuth is off. Turn it on, as the same section describes.
64
86
  - `kestrel login` or `kestrel api` reports a `401`. Access signed you in, but Kestrel refused the sign-in. If you set `ACCESS_ALLOWED_EMAILS`, check that it includes the address you signed in with. If either names `access_unidentified`, it's the next problem. If `kestrel api` says your sign-in has ended, sign in again as [Connect Claude](08-connect-claude.md#claude-code) describes.
87
+ - `kestrel api` says it couldn't renew your sign-in because its Access team answered 404 (not found). First check which instance it called: when you don't name one, it prints a line naming it before "the only instance you're signed in to", which may not be the one you meant. Name yours with `--url`. If it is yours, the Access team domain was most likely renamed or removed since you signed in. Sign in again with `npx kestrel login` and your instance's address, which finds the team it uses now.
65
88
  - **"Access let you in, but didn't say who you are".** Your login carried neither an email nor a service token. Some other kind of Access policy let it through, such as a certificate or device rule, or a login method that gives no email. Kestrel accepts only an Allow policy on people's emails and a Service Auth policy on a service token. Sign out, then sign in through the policy that lets you in by email. Whoever runs your instance removes the other policy from its Access application, or changes it to one of those two kinds. A script that gets this needs a service token under a Service Auth policy, as [Allow the token in](../guides/02-automate.md#2-allow-the-token-in) describes.
66
89
  - **Claude's changes read "via the API".** Claude called the API some way other than `kestrel api`, which names Claude Code. Your instance's instructions for Claude Code have it use `kestrel api` once they're brought up to date, as [Install the release](../guides/07-upgrade.md#2-install-the-release) describes. Changes reading "a service token" mean Claude still uses one, and [Connect Claude](08-connect-claude.md#claude-code) signs it in as you instead.
67
90
 
@@ -76,9 +99,10 @@ With an address set, Kestrel emails you:
76
99
  - when a send finishes, with how many emails were accepted, unsent, and skipped
77
100
  - when your provider refuses your account or a send, or no provider is connected
78
101
  - when the SES sandbox refuses a signup's confirmation, at most once a day
102
+ - when email isn't going out and signups are paused, found by a reader's signup or the check of your SES account, at most once a day while it lasts
79
103
  - when a send stops going out with nothing to explain it, waits for you to resolve it, or misses its time
80
104
  - when a service token imports a list, since no one saw it happen (an import you make yourself, or through Claude, sends nothing)
81
105
 
82
- Each links to the page it's about, and is sent once while the problem lasts. A bounce spike shows on the dashboard but isn't emailed. **Send a test notification** checks that one arrives.
106
+ Each goes to every address set, links to the page it's about, and is sent once while the problem lasts. If one address can't be reached, the others still get it, and **Last notification** names the one that failed. A bounce spike shows on the dashboard but isn't emailed. **Send a test notification** checks that one arrives.
83
107
 
84
108
  By default, notifications go through your newsletter's own email provider. So when that provider refuses your account, the notification about it can't reach you either. You can send them another way, as [Notifications through Cloudflare's email](../guides/03-notifications.md) describes.
@@ -14,7 +14,7 @@ How you connect depends on where you talk to Claude:
14
14
  1. In Claude, go to **Customize → Connectors**, select **Add**, then **Add custom connector**.
15
15
  1. Name it, and enter your publication's address followed by `/api/mcp`, such as `https://newsletter.example.com/api/mcp`.
16
16
  1. Under **OAuth client**, choose **Register automatically**.
17
- 1. Select **Add**, then sign in the way you do for the dashboard, and approve the sign-in.
17
+ 1. Select **Add**, then **Connect**. Sign in the way you do for the dashboard if asked, and choose **Allow**.
18
18
 
19
19
  On a Team or Enterprise plan, an Owner of your Claude organization adds the connector under **Organization settings → Connectors** instead, and you then select **Connect** on it. Anthropic's [custom connectors guide](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) has the details.
20
20
 
@@ -2,7 +2,9 @@
2
2
 
3
3
  Your publication's subscribe page is at `/subscribe` on your publication's address, such as `https://newsletter.example.com/subscribe`. Copy it from the dashboard's checklist or **Settings → Ways to subscribe**, and share it wherever your readers are. A reader joins only once they confirm by email.
4
4
 
5
- **Signups open only once an email provider is connected.** Until then, the page says the newsletter isn't taking subscribers yet, and the dashboard says so too. You can share the link anyway, and it opens once email is connected ([Connect Amazon SES](../get-started/05-ses.md)).
5
+ **Signups open only once email is working.** Until an email provider is connected and has sent an email, such as a test from **Template → Send test email**, the page says the newsletter isn't taking subscribers yet. If your provider starts refusing your email, the page says signups are paused, and the dashboard says what to fix. Either way every visitor gets the same page and nothing is saved. You can share the link anyway, and it opens once email works ([Connect Amazon SES](../get-started/05-ses.md)).
6
+
7
+ **On Amazon SES, share it once AWS grants production access.** Until then, SES mails only addresses verified in it, so a reader who signs up never gets their confirmation email. The dashboard's checklist and an empty **Subscribers** list say so, and link [Request production access](../get-started/06-production-access.md). Importing your list is safe meanwhile, since an import sends no email.
6
8
 
7
9
  ## How a reader joins
8
10
 
@@ -1,25 +1,29 @@
1
1
  # Check your setup
2
2
 
3
- Before your readers get anything, check three things yourself: your email arrives and replies reach you, subscribing works, and problems will reach you.
3
+ Three checks before your readers get anything: is email getting out, will you hear about problems, and what will your readers get. The dashboard's setup steps tick each one off from what Kestrel sees, so you, and Claude, can tell what's left.
4
4
 
5
- **Email has to be connected first.** Until it is, nothing can be sent, and the dashboard's checklist shows **Connect Amazon SES** in place of this step, as [Connect Amazon SES](../get-started/05-ses.md) describes.
5
+ **Email has to be connected first.** Until it is, nothing can be sent, and **Send a test email** waits on the dashboard's **Connect Amazon SES** step, which [Connect Amazon SES](../get-started/05-ses.md) covers.
6
6
 
7
- **Until AWS grants production access, Amazon SES mails only addresses verified in SES.** So test with a verified address, and hold the subscribe link until then. The checklist adds **Request production access** after this step, and checks it off once AWS grants it, as [Request production access](../get-started/06-production-access.md) describes.
7
+ **Until AWS grants production access, Amazon SES mails only addresses verified in SES.** So test with a verified address, and hold the subscribe link until then. The dashboard's **Request production access** step says where AWS's review stands, and [Request production access](../get-started/06-production-access.md) covers the request.
8
8
 
9
- ## 1. Send yourself a test
9
+ ## 1. Send a test email
10
10
 
11
- Choose **Template** in the sidebar, then **Send test email**, and send a sample post to your own inboxes. When it arrives, check:
11
+ Choose **Template** in the sidebar, then **Send test email**, and send a sample post to your own inboxes. This is the test that proves email gets out: signups stay closed until SES takes an email, and the step is checked off once it does. When it arrives, check:
12
12
 
13
13
  - **The From name and address,** which readers see first. They're set when your instance is deployed.
14
14
  - **Where it lands:** the inbox, Promotions, or spam. If it lands in spam, check your sending domain's records, as [Sending-domain DNS](../reference/02-sending-domain-dns.md) describes.
15
15
  - **A reply.** Reply to the test, and check that it reaches your inbox. Kestrel never sees replies, so this depends on [forwarding your From address](../get-started/05-ses.md#8-receive-replies-at-your-from-address).
16
16
 
17
- ## 2. Subscribe yourself
17
+ If SES refuses the test, the dashboard says why in red above the steps, with what to check. Send another test once it's fixed.
18
18
 
19
- Open your subscribe page from **Settings → Ways to subscribe**, subscribe with a personal address, and confirm. You see what a new reader sees, and you appear under **Subscribers** as **Confirmed**.
19
+ ## 2. Get notified
20
+
21
+ Under **Settings → Notifications**, add your address under **Send notifications to** with **Add inbox**, save, and choose **Send a test notification**. It goes to each address. If someone else runs your instance, add their address too, so a problem reaches both of you. Kestrel then emails you when a send finishes or needs you, as [Notifications](06-troubleshooting.md#notifications) lists. The step is checked off once an address is saved and the last notification arrived. If the last notification didn't arrive, the dashboard says so above the steps, with the channel's words.
20
22
 
21
- ## 3. Set your notifications address
23
+ ## 3. See what your readers get
24
+
25
+ Open your subscribe page from **Settings → Ways to subscribe**, subscribe with a personal address, and confirm. You see what a new reader sees, and you appear under **Subscribers** as **Confirmed**.
22
26
 
23
- Under **Settings → Notifications**, fill in **Send notifications to**, save, and choose **Send a test notification**. Kestrel then emails you when a send finishes or needs you, as [Notifications](06-troubleshooting.md#notifications) lists.
27
+ Then, once your first post is scheduled, **Send a test email for your first post** from its page. Scheduling makes the exact email your list will get, so this test is what goes out, and the step is checked off once it's sent. A test sent from a draft is a preview and doesn't count, and a change to your template or publication identity re-makes the scheduled email, so the step opens again until you test the new one. [Write and send a post](04-write-a-post.md) covers scheduling.
24
28
 
25
29
  **If you're bringing more than about 1,000 subscribers, upgrade to Cloudflare's Workers Paid plan before you import.** [Bring your existing list](01-your-list.md#limits) explains why.
@@ -4,12 +4,17 @@ These pages get you publishing in the dashboard, and cover the decisions to make
4
4
 
5
5
  ## Set up your publication
6
6
 
7
- These are the steps to set up your publication. If you're starting without a list, skip importing one.
7
+ These are the dashboard's setup steps, in its order. Each is checked off from what Kestrel can see, which Claude reads too, except sharing your link, which Kestrel can't see. Connecting Amazon SES and requesting production access are for whoever runs your Kestrel instance, in [Connect Amazon SES](../get-started/05-ses.md) and [Request production access](../get-started/06-production-access.md). If you're starting without a list, skip importing one.
8
8
 
9
+ 1. Connect Amazon SES
10
+ 1. [Send a test email](10-check-your-setup.md)
9
11
  1. [Name your publication](03-name-your-publication.md)
10
- 1. [Check your setup](10-check-your-setup.md)
12
+ 1. Get notified
13
+ 1. Request production access
11
14
  1. [Bring your existing list](01-your-list.md)
12
- 1. [Write and send a post](04-write-a-post.md)
15
+ 1. [Write your first post](04-write-a-post.md)
16
+ 1. Schedule your first post
17
+ 1. Send a test email for your first post
13
18
  1. [Share your subscribe link](09-share-your-subscribe-link.md)
14
19
 
15
20
  ## Go further
@@ -68,7 +68,7 @@ Vars go in the environment block's `vars`. Every value is a string.
68
68
 
69
69
  - `FROM_ADDRESS` required with a real provider
70
70
 
71
- The `From:` of every email, and the identity your provider authenticates. Refused unless it's an email address, optionally with a display name in front, like `Name <address>`, and never on `example.com`, unless `PROVIDER` is `fake`.
71
+ The `From:` of every email, and the identity your provider authenticates. Refused unless it's an email address, optionally with a display name in front, like `Name <address>`, and never on `example.com`, unless `PROVIDER` is `fake`. With Amazon SES, when no identity covering it is verified for sending in `AWS_REGION`, **Settings → Email sender** says so in red within about ten minutes, without sending anything.
72
72
 
73
73
  It isn't the publication's name, tagline, or logo. You set those in the editor, under **Settings → Publication identity**. Until you do, the From address's display name stands in for the name, and with neither, the emails and reader pages show no name at all.
74
74
 
@@ -138,11 +138,11 @@ Store each with `npx wrangler secret put NAME --env production`, which prompts f
138
138
 
139
139
  - `ACCESS_ALLOWED_EMAILS` optional
140
140
 
141
- A comma-separated list of the people allowed to sign in. Without it, the app lets in anyone the Access policy allows, so it keeps the editor limited to named people if the policy is widened later. It applies to people only. A service token, the credential a script uses, carries no email, so the Access policy alone decides whether it gets in.
141
+ A comma-separated list of the people allowed to sign in, such as `you@example.com, publisher@example.com`. Spaces and capital letters don't matter. Without it, the app lets in anyone the Access policy allows, so it keeps the editor limited to named people if the policy is widened later. It applies to people only. A service token, the credential a script uses, carries no email, so the Access policy alone decides whether it gets in.
142
142
 
143
143
  - `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` required with SES
144
144
 
145
- The IAM user's access key, which sends through SES.
145
+ The IAM user's access key, which sends through SES. When SES rejects it, because the key ID is unknown, the secret doesn't match it, or the AWS account isn't signed up for SES, **Settings → Email sender** says so in red within about ten minutes, without sending anything.
146
146
 
147
147
  - `SNS_TOPIC_ARN` required with SES
148
148
 
@@ -150,7 +150,7 @@ Store each with `npx wrangler secret put NAME --env production`, which prompts f
150
150
 
151
151
  - `SES_CONFIGURATION_SET` optional, recommended with SES
152
152
 
153
- The configuration set named on every email, so SES reports bounces and complaints. A name SES can't find in the region holds every list send until it's fixed.
153
+ The configuration set named on every email, so SES reports bounces and complaints. A name SES can't find in the region holds every list send until it's fixed. **Settings → Email sender** says so in red within about ten minutes, without sending anything, and does the same when the set's sending is turned off, as long as the app's key may call `ses:GetConfigurationSet`.
154
154
 
155
155
  - `DEV_AUTH_SECRET` never on a deployed environment
156
156
 
@@ -196,7 +196,7 @@ Wrangler doesn't carry bindings or vars over from the top level of `wrangler.jso
196
196
 
197
197
  ## What the app refuses
198
198
 
199
- The app checks its configuration on every request. It refuses to run on one that would do the wrong thing quietly, and each setting above says what it refuses. In local development, `SIMULATE_SENDS` must also be off (`off`, `0`, `false`, `no`, or empty), `not-connected` to run as a deployment with no email provider yet, or the provider a local send models: `ses` or `generic` (also `1`, `on`, `true`, or `yes`), optionally followed by `:none` for no injected failures or `:realistic`, the default. `SIMULATE_ACCOUNT` must be off, or, beside `SIMULATE_SENDS=ses`, the SES account state local development stands in for: `sandbox`, `pending`, `denied`, `production`, or `permission`, optionally followed by `:unrouted`. Case doesn't matter. A deployed instance ignores both.
199
+ The app checks its configuration on every request. It refuses to run on one that would do the wrong thing quietly, and each setting above says what it refuses. In local development, `SIMULATE_SENDS` must also be off (`off`, `0`, `false`, `no`, or empty), `not-connected` to run as a deployment with no email provider yet, or the provider a local send models: `ses` or `generic` (also `1`, `on`, `true`, or `yes`), optionally followed by `:none` for no injected failures or `:realistic`, the default, and, for `ses`, by `:refuse=` and a cause SES refuses every email for (`credentials`, `opt_in`, `configuration`, `sender`, `mail_from`, `paused`, `set_paused`, `suspended`, or `unsupported`), as in `ses:refuse=configuration`. `SIMULATE_ACCOUNT` must be off, or, beside `SIMULATE_SENDS=ses`, the SES account state local development stands in for: `sandbox`, `pending`, `denied`, `production`, or `permission`, optionally followed by `:unrouted` or by one account problem, which shows its warning: `:set_missing`, `:set_paused`, `:key_rejected`, `:sending_disabled`, `:shutdown`, `:sender_unverified`, or `:probation`, as in `production:set_missing`. Case doesn't matter. A deployed instance ignores both.
200
200
 
201
201
  Until it's fixed, no sends go out, and each place says what its reader can act on:
202
202