@kurtbruns/kestrel 1.19.0 → 1.21.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 +69 -1
- package/bin/kestrel.mjs +1 -1
- package/cli/seed.mjs +2 -8
- package/dist/public/dashboard/app.js +307 -306
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +90 -51
- package/dist/worker/index.js +890 -559
- package/docs/get-started/02-deploy.md +3 -3
- package/docs/get-started/03-access.md +8 -4
- package/docs/get-started/04-aws-account.md +2 -0
- package/docs/get-started/05-ses.md +4 -2
- package/docs/get-started/07-verify.md +8 -6
- package/docs/get-started/08-go-live.md +12 -18
- package/docs/guides/01-allow-claude.md +7 -2
- package/docs/guides/03-notifications.md +17 -13
- package/docs/guides/07-upgrade.md +16 -2
- package/docs/publishing/03-name-your-publication.md +8 -0
- package/docs/publishing/04-write-a-post.md +2 -2
- package/docs/publishing/06-troubleshooting.md +52 -26
- package/docs/publishing/11-email-template.md +1 -1
- package/docs/publishing/README.md +1 -1
- package/docs/reference/02-sending-domain-dns.md +3 -3
- package/migrations/0021_notifications_outage_receipts.sql +46 -0
- package/package.json +10 -10
- package/packages/kestrel/lib/api.mjs +19 -12
- package/packages/kestrel/lib/login.mjs +2 -2
- package/packages/kestrel/lib/media-types.mjs +34 -0
- package/packages/kestrel/lib/oauth.mjs +19 -2
- package/packages/kestrel/lib/openapi.mjs +1 -1
- package/template/README.md +1 -1
- package/template/claude/CLAUDE.md +1 -1
- package/template/claude/skills/upgrade/SKILL.md +1 -0
|
@@ -167,7 +167,7 @@ Create Kestrel's tables in the production database:
|
|
|
167
167
|
npx wrangler d1 migrations apply DB --remote --env production
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
It lists the migrations it's about to apply and asks you to confirm. It applies only the ones the database hasn't seen, so running it again is harmless.
|
|
170
|
+
It lists the migrations it's about to apply and asks you to confirm. Run by Claude, or in any other shell that isn't interactive, it answers its own prompt and applies them without asking. It applies only the ones the database hasn't seen, so running it again is harmless.
|
|
171
171
|
|
|
172
172
|
## 6. Deploy
|
|
173
173
|
|
|
@@ -175,7 +175,7 @@ It lists the migrations it's about to apply and asks you to confirm. It applies
|
|
|
175
175
|
npx wrangler deploy --env production
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
This deploys the release your instance runs, with your settings.
|
|
178
|
+
This deploys the release your instance runs, with your settings. Near its end, the output names the hostname the app now answers on, then its schedule and a version ID. Deploying also registers the once-a-minute schedule that sends posts, which you can see under the Worker's **Triggers** tab in the Cloudflare dashboard.
|
|
179
179
|
|
|
180
180
|
Always name `--env production`, here and on every wrangler command that stores a secret. Without it, wrangler deploys the top level, local development, as a second Worker that answers nowhere and never sends. It takes the top level's `name` in `wrangler.jsonc`, such as `newsletter-dev`. If that happens, remove it by that name:
|
|
181
181
|
|
|
@@ -195,7 +195,7 @@ npx wrangler delete --name newsletter-dev
|
|
|
195
195
|
{"status":"ok","service":"kestrel"}
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
If `curl` reports a certificate error
|
|
198
|
+
If `curl` reports a certificate error or can't resolve the host, Cloudflare is still setting up the hostname. Or your computer remembers it from before it existed. Try again over the next 30 minutes.
|
|
199
199
|
|
|
200
200
|
1. The editor is locked. Until you set up Access in the next step, the app can't tell who is asking, so it lets no one in:
|
|
201
201
|
|
|
@@ -37,7 +37,7 @@ 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 home page, the archive, subscribing, unsubscribing, images, and your email provider's webhooks must stay public.
|
|
39
39
|
|
|
40
|
-
1. Add only these two public hostnames. If the form also shows an empty private hostname or IP row, remove it
|
|
40
|
+
1. Add only these two public hostnames. If the form also shows an empty private hostname or IP row, remove it. Otherwise saving fails with an error naming `use_clientless_isolation_app_launcher_url`, saying it needs private destinations.
|
|
41
41
|
|
|
42
42
|
1. Name the application after your app's hostname, `newsletter.example.com`. Cloudflare suggests a name made from the subdomain, which won't tell two newsletters apart.
|
|
43
43
|
|
|
@@ -55,7 +55,7 @@ Every Access application denies everyone until a policy allows them.
|
|
|
55
55
|
|
|
56
56
|
1. Select **Create**.
|
|
57
57
|
|
|
58
|
-
Cloudflare's own login is on by default, and admits members of your Cloudflare account, so the email in the policy you made should be one.
|
|
58
|
+
Cloudflare's own login is on by default, and admits members of your Cloudflare account, so the email in the policy you made should be one.
|
|
59
59
|
|
|
60
60
|
## 4. Give the app its two values
|
|
61
61
|
|
|
@@ -93,8 +93,6 @@ The app verifies each login against your team domain, and checks that it was iss
|
|
|
93
93
|
|
|
94
94
|
Spaces and capital letters don't matter. Without the list, the app lets in anyone your Access policy allows.
|
|
95
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.
|
|
97
|
-
|
|
98
96
|
## Check it
|
|
99
97
|
|
|
100
98
|
1. In a private browser window, open `https://newsletter.example.com/dashboard/`. Cloudflare asks you to sign in.
|
|
@@ -123,4 +121,10 @@ If a check fails:
|
|
|
123
121
|
- **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.
|
|
124
122
|
- **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.
|
|
125
123
|
|
|
124
|
+
## Allow someone else in
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
If they don't have a Cloudflare account, add another way to sign in, such as a one-time code by email, under **Zero Trust → Integrations → Identity providers**.
|
|
129
|
+
|
|
126
130
|
Next, [create an AWS account](04-aws-account.md).
|
|
@@ -65,4 +65,6 @@ AWS charges for what you use, with no spending limit, so a mistake or a leaked k
|
|
|
65
65
|
|
|
66
66
|
1. Under **Budgets**, your budget is listed, and its email recipient shows as **Active**.
|
|
67
67
|
|
|
68
|
+
If it shows as **Pending**, select **Resend verification** on its row, and verify the address as section 4 describes.
|
|
69
|
+
|
|
68
70
|
Next, [connect Amazon SES](05-ses.md).
|
|
@@ -47,9 +47,11 @@ The same form sets a custom MAIL FROM domain, so SPF also passes for your domain
|
|
|
47
47
|
|
|
48
48
|
1. Wait until SES shows **Identity status** as **Verified**, and both **DKIM configuration** and **MAIL FROM configuration** as **Successful**.
|
|
49
49
|
|
|
50
|
+
If they stay pending, check that each record's proxy status is **DNS only**.
|
|
51
|
+
|
|
50
52
|
## 2. Add a DMARC record
|
|
51
53
|
|
|
52
|
-
DMARC tells inboxes what to do with mail that fails its checks, and
|
|
54
|
+
DMARC tells inboxes what to do with mail that fails its checks, and can ask them to send you reports. The `rua` tag that asks for them is optional, and the policy works without it. SES doesn't add this record, so you add it yourself. It starts in monitor mode, `p=none`, which blocks nothing, your own mail included. It only monitors: inboxes still deliver mail that fails, so it doesn't stop anyone sending as your domain.
|
|
53
55
|
|
|
54
56
|
1. In the Cloudflare dashboard, open your domain's DNS records, and add a record:
|
|
55
57
|
|
|
@@ -307,7 +309,7 @@ Replies to a post go to your From address, `newsletter@send.example.com`, which
|
|
|
307
309
|
|
|
308
310
|
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.
|
|
309
311
|
|
|
310
|
-
1. **(Optional)**
|
|
312
|
+
1. **(Optional)** Forward your DMARC reports too, if you collect them and the address in your DMARC record doesn't receive mail. Add a rule that forwards `dmarc` the same way. Then change the record's `rua` to `mailto:dmarc@send.example.com`. Reports come about once a day from each inbox provider that received your mail. A day or two after your next email goes out, check that they arrive. Email Routing's **Activity log** shows each forward's status.
|
|
311
313
|
|
|
312
314
|
1. Check that your From address receives mail. This now lists Cloudflare's three `route` mail servers:
|
|
313
315
|
|
|
@@ -17,7 +17,7 @@ A test email goes through the same code as a real send. If the test looks right,
|
|
|
17
17
|
|
|
18
18
|
1. In the editor at `https://newsletter.example.com/dashboard/`, create a post with a subject, a short body, and an image.
|
|
19
19
|
|
|
20
|
-
1. Select **Send test email**, enter your address, and send it.
|
|
20
|
+
1. Select **Send test email**, enter your address, and send it. If SES refuses it, the test's line says why and what fixes it.
|
|
21
21
|
|
|
22
22
|
1. Open the email. It arrives in your inbox, not in spam, the image loads, and the subject reads as you wrote it. Its links work, but a test email unsubscribes no one; section 5 unsubscribes for real.
|
|
23
23
|
|
|
@@ -45,7 +45,7 @@ Kestrel emails each notifications address when a send finishes, and right away w
|
|
|
45
45
|
|
|
46
46
|
1. In the editor, open **Settings → Notifications**, type that address under **Send notifications to**, select **Add inbox**, and then **Save**.
|
|
47
47
|
|
|
48
|
-
1. Select **Send a test notification**. It arrives at that address.
|
|
48
|
+
1. Select **Send a test notification**. It arrives at that address. If it doesn't, **Last notification**, on the same page, says what happened.
|
|
49
49
|
|
|
50
50
|
The publisher sets their own address in [Check your setup](../publishing/10-check-your-setup.md).
|
|
51
51
|
|
|
@@ -65,11 +65,13 @@ This is a real send, to a list of one. It proves the whole path: subscribing, co
|
|
|
65
65
|
|
|
66
66
|
1. When the send finishes, the post arrives at your second address. A notification arrives at your first, with the subject `Sent:` and the post's title.
|
|
67
67
|
|
|
68
|
+
If the send shows in red under **Sent**, its line names the fix. Once that's fixed, the send resumes on its own.
|
|
69
|
+
|
|
68
70
|
## 5. Unsubscribe
|
|
69
71
|
|
|
70
|
-
Every email carries an unsubscribe link, and a header that lets mail clients show their own **Unsubscribe** button.
|
|
72
|
+
Every email carries an unsubscribe link, and a header that lets mail clients show their own **Unsubscribe** button. The button works in one step, and the link asks you to confirm first. Neither needs a login.
|
|
71
73
|
|
|
72
|
-
1. Open the post from section 4 in your mail client. It shows an **Unsubscribe** button next to the sender.
|
|
74
|
+
1. Open the post from section 4 in your mail client. It shows an **Unsubscribe** button next to the sender. If your client shows none, use the **Unsubscribe** link in the email's footer, which asks you to confirm on a page.
|
|
73
75
|
|
|
74
76
|
1. Select it. In the editor, **Subscribers** shows the address as **Unsubscribed**, and later sends leave it out.
|
|
75
77
|
|
|
@@ -94,13 +96,13 @@ The app writes a log line for each thing it does, and Cloudflare keeps them for
|
|
|
94
96
|
|
|
95
97
|
1. In the Cloudflare dashboard, go to **Workers & Pages**, select your Worker, the `name` in the `production` block of `wrangler.jsonc`, and select **Observability**.
|
|
96
98
|
|
|
97
|
-
1. Filter on `event` equal to `sweep.tick`. A line arrives every minute.
|
|
99
|
+
1. Filter on `event` equal to `sweep.tick`. A line arrives every minute. If none arrive, the schedule probably isn't running, and deploying again with `--env production` registers it.
|
|
98
100
|
|
|
99
101
|
1. Filter on `event` equal to `send.completed`. Your send from section 4 is there.
|
|
100
102
|
|
|
101
103
|
1. Filter on `event` equal to `send.scheduled`. Your two sends from section 4 are there, and `send.canceled` shows the one you canceled. Each line that records an action says who took it. Its `principal` is `human` or `service`, and its `client` names the program a person used, such as `editor`.
|
|
102
104
|
|
|
103
|
-
1. Filter on `level` equal to `error`. A healthy instance shows nothing.
|
|
105
|
+
1. Filter on `level` equal to `error`. A healthy instance shows nothing. An occasional `d1.unavailable` line at `warn` is a brief database stall the next run makes up for, and needs nothing. The same line every minute means work isn't getting done, so if you set up alerts, alert on it repeating as well as on `error`.
|
|
104
106
|
|
|
105
107
|
To follow one send, narrow the time range and filter on its `sendId`, since a long range may show only a sample. On the Free plan, Cloudflare stops keeping logs for the rest of the day once its daily allowance is used, as [its pricing page](https://developers.cloudflare.com/observability/pricing/) describes.
|
|
106
108
|
|
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
# Go live
|
|
2
2
|
|
|
3
|
-
At this point, your instance should be deployed and verified. Before anyone subscribes, check that AWS granted production access
|
|
3
|
+
At this point, your instance should be deployed and verified. Before anyone subscribes, [check that AWS granted production access](06-production-access.md#4-check-that-aws-granted-it), so SES can send to anyone. From here, where you go next depends on who's writing the newsletter.
|
|
4
4
|
|
|
5
5
|
## Start publishing
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Open the editor at `https://newsletter.example.com/dashboard/`. The dashboard's setup steps show what's left, and [Publishing](../publishing/README.md) covers each one.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Hand off to someone else
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
[Let them into the editor](03-access.md#allow-someone-else-in), and add their address beside yours under **Settings → Notifications**, so a problem reaches both of you. Then send them the editor's address and a link to [Publishing](../publishing/README.md). The dashboard's setup steps show them what's left.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Optional guides
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Each one adds one thing, and you can come back to any of them later.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [Allow Claude to connect](../guides/01-allow-claude.md), so the publisher can connect Claude Desktop, claude.ai, or Claude Code, and have it draft, proofread, and schedule posts as them, through the same API the editor uses.
|
|
24
|
-
- [Automate with a service token](../guides/02-automate.md), so a script can call the API with a service token, when no one is signed in.
|
|
25
|
-
- [Notifications through Cloudflare's email](../guides/03-notifications.md), so problems are reported even when the email provider has stopped sending.
|
|
26
|
-
- [Rate-limit the subscribe form](../guides/05-rate-limit.md), before the subscribe link is shared widely.
|
|
27
|
-
- [As your list grows](../guides/09-as-your-list-grows.md), from about 1,000 subscribers.
|
|
28
|
-
- [Upgrade to a new release](../guides/07-upgrade.md), when one comes out.
|
|
17
|
+
- Before you share your subscribe link: [Rate-limit the subscribe form](../guides/05-rate-limit.md)
|
|
18
|
+
- To hear about a problem even when SES has stopped sending: [Notifications through Cloudflare's email](../guides/03-notifications.md)
|
|
19
|
+
- To have Claude draft, proofread, and schedule posts: [Allow Claude to connect](../guides/01-allow-claude.md)
|
|
20
|
+
- To run scripts with no one signed in: [Automate with a service token](../guides/02-automate.md)
|
|
21
|
+
- To see when your list needs Workers Paid or higher SES limits: [As your list grows](../guides/09-as-your-list-grows.md)
|
|
22
|
+
- To move to a new release: [Upgrade to a new release](../guides/07-upgrade.md)
|
|
@@ -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
|
-
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
|
|
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` (`OAuth not enabled for the targeted resource`); wait, then try again.
|
|
47
47
|
|
|
48
48
|
## 2. Connect Claude, or send the publisher the address
|
|
49
49
|
|
|
@@ -65,4 +65,9 @@ Anyone the Access application's Allow policy lets into the dashboard can connect
|
|
|
65
65
|
|
|
66
66
|
1. Ask Claude what the publication is called. It answers with the name the dashboard shows.
|
|
67
67
|
|
|
68
|
-
If
|
|
68
|
+
If a check fails:
|
|
69
|
+
|
|
70
|
+
- **Claude can't connect, or the sign-in fails.** Check section 1's settings, and that the connector's address is the instance's address followed by `/api/mcp`.
|
|
71
|
+
- `kestrel login` says the instance "doesn't offer a sign-in for programs". Managed OAuth is off. Turn it on, as section 1 describes.
|
|
72
|
+
- `kestrel login` or `kestrel api` reports a `401`. If you set `ACCESS_ALLOWED_EMAILS`, check that it includes the person's address.
|
|
73
|
+
- **The sign-in says Access didn't say who the person is.** The same goes for a `401` naming `access_unidentified`. The app accepts only an Allow policy on people's emails and a Service Auth policy on a service token. Remove or change any other kind.
|
|
@@ -19,7 +19,7 @@ Cloudflare's email sends only from a domain set up with Email Routing. Use your
|
|
|
19
19
|
|
|
20
20
|
1. Add `newsletter.example.com` to Email Routing. Cloudflare adds subdomains from your domain's own settings: select `example.com`, then **Settings**, and add `newsletter` under **Subdomains**.
|
|
21
21
|
|
|
22
|
-
1. Cloudflare adds
|
|
22
|
+
1. Cloudflare adds the DNS records it needs for `newsletter.example.com`. Wait until the new row reads **Status: Enabled** and **DNS records: Locked**.
|
|
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
|
|
|
@@ -27,27 +27,25 @@ Avoid turning on Email Routing for a domain that already receives mail elsewhere
|
|
|
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. 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
|
|
30
|
+
1. Under **Email Routing**, go to **Destination addresses**, and add each address saved for notifications that isn't already verified there.
|
|
31
31
|
|
|
32
|
-
1. Open the email Cloudflare sends to each
|
|
32
|
+
1. Open the email Cloudflare sends to each one, and select **Verify email address**. If it's the publisher's address, ask them to do this step.
|
|
33
33
|
|
|
34
|
-
A verified address belongs to your Cloudflare account, so every environment in it can use it.
|
|
34
|
+
A verified address belongs to your Cloudflare account, so every environment in it can use it. If the publisher adds or changes a notifications address later, verify the new one too.
|
|
35
35
|
|
|
36
36
|
## 3. Add the binding and deploy
|
|
37
37
|
|
|
38
38
|
A `send_email` binding named `NOTIFY` moves notifications to Cloudflare's email. Without it, they go through your provider.
|
|
39
39
|
|
|
40
|
-
1. In `wrangler.jsonc`, add the binding to
|
|
40
|
+
1. In `wrangler.jsonc`, add the binding to `env.production`, beside `r2_buckets` and not inside `vars`:
|
|
41
41
|
|
|
42
42
|
```jsonc
|
|
43
|
-
"send_email": [
|
|
44
|
-
{ "name": "NOTIFY", "allowed_destination_addresses": ["you@example.com"] } // ← the notifications addresses
|
|
45
|
-
],
|
|
43
|
+
"send_email": [{ "name": "NOTIFY" }],
|
|
46
44
|
```
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
Give the binding only its name. This file is committed to your repository, so keep the notifications addresses out of it.
|
|
49
47
|
|
|
50
|
-
1. **(Optional)** Notifications come from `Kestrel <kestrel@newsletter.example.com>`. To use another address on that hostname, set `NOTIFY_FROM` in
|
|
48
|
+
1. **(Optional)** Notifications come from `Kestrel <kestrel@newsletter.example.com>`. To use another address on that hostname, set `NOTIFY_FROM` in `env.production.vars`:
|
|
51
49
|
|
|
52
50
|
```jsonc
|
|
53
51
|
"NOTIFY_FROM": "Kestrel <alerts@newsletter.example.com>",
|
|
@@ -62,7 +60,8 @@ A `send_email` binding named `NOTIFY` moves notifications to Cloudflare's email.
|
|
|
62
60
|
1. Commit the change, and push it:
|
|
63
61
|
|
|
64
62
|
```bash
|
|
65
|
-
git
|
|
63
|
+
git add wrangler.jsonc
|
|
64
|
+
git commit -m "Send notifications through Cloudflare's email"
|
|
66
65
|
git push
|
|
67
66
|
```
|
|
68
67
|
|
|
@@ -72,17 +71,22 @@ A `send_email` binding named `NOTIFY` moves notifications to Cloudflare's email.
|
|
|
72
71
|
npx wrangler deploy --env production
|
|
73
72
|
```
|
|
74
73
|
|
|
74
|
+
The output lists the binding as `env.NOTIFY (unrestricted)`. Cloudflare still delivers only to the addresses you verified.
|
|
75
|
+
|
|
75
76
|
## Check it
|
|
76
77
|
|
|
77
78
|
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
79
|
|
|
79
|
-
1. Select **Send a test notification**.
|
|
80
|
+
1. Select **Send a test notification**. **Last notification** shows it as delivered.
|
|
81
|
+
|
|
82
|
+
1. Check that the test arrives at each notifications address. If it's in the spam folder, mark it **Not spam**.
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
1. In Email Routing's **Activity log**, the test shows as **Delivered**. Go by this rather than Email Routing's summary, which [Cloudflare says](https://developers.cloudflare.com/email-service/platform/limits/) can list it as dropped even when it's delivered.
|
|
82
85
|
|
|
83
86
|
## What notifications don't cover
|
|
84
87
|
|
|
85
88
|
- **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/07-verify.md#7-read-the-logs) to check that `sweep.tick` still arrives every minute.
|
|
89
|
+
- **The database being down.** Noticing a problem and telling you both need the database. [Read the logs](../get-started/07-verify.md#7-read-the-logs) to check for `d1.unavailable` repeating.
|
|
86
90
|
- **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
91
|
- **Problems while no address is set.** With the address blank, the app sends nothing, and saves nothing up for later.
|
|
88
92
|
|
|
@@ -16,6 +16,14 @@ A release can ask you to change a setting or a record before it runs. The change
|
|
|
16
16
|
|
|
17
17
|
Each step here changes only your instance's repository. Nothing reaches your deployed app until section 5.
|
|
18
18
|
|
|
19
|
+
1. Start from a clean repository, so the upgrade's commit holds only the upgrade and going back undoes nothing else:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git status
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If it lists anything, commit it first, as its own commit, or move it out of the repository.
|
|
26
|
+
|
|
19
27
|
1. Install the release. Replace `X.Y.Z` with its version:
|
|
20
28
|
|
|
21
29
|
```bash
|
|
@@ -24,6 +32,12 @@ Each step here changes only your instance's repository. Nothing reaches your dep
|
|
|
24
32
|
|
|
25
33
|
Your `package.json` now names exactly that version. Kestrel's versions aren't semantic versioning, so your instance never moves to a release you didn't choose.
|
|
26
34
|
|
|
35
|
+
If npm stops with `ERESOLVE` and names wrangler, your `package.json` pins a wrangler older than the release needs. The error's `peer wrangler@` line gives the range it needs. Install both together, with that range in place of `WRANGLER_RANGE`:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @kurtbruns/kestrel@X.Y.Z "wrangler@WRANGLER_RANGE"
|
|
39
|
+
```
|
|
40
|
+
|
|
27
41
|
1. Bring in the release's database migrations:
|
|
28
42
|
|
|
29
43
|
```bash
|
|
@@ -91,7 +105,7 @@ A release changes the database only by adding a migration. Applying it keeps eve
|
|
|
91
105
|
npx wrangler d1 migrations apply DB --remote --env production
|
|
92
106
|
```
|
|
93
107
|
|
|
94
|
-
It lists the migrations it's about to apply, and asks you to confirm. When a release has none, it says there's nothing to apply. Run it on every upgrade.
|
|
108
|
+
It lists the migrations it's about to apply, and asks you to confirm. Run by Claude, or in any other shell that isn't interactive, it answers its own prompt and applies them without asking. When a release has none, it says there's nothing to apply. Run it on every upgrade.
|
|
95
109
|
|
|
96
110
|
## 5. Deploy
|
|
97
111
|
|
|
@@ -99,7 +113,7 @@ It lists the migrations it's about to apply, and asks you to confirm. When a rel
|
|
|
99
113
|
npx wrangler deploy --env production
|
|
100
114
|
```
|
|
101
115
|
|
|
102
|
-
|
|
116
|
+
Near its end, the output names your app's hostname, then its schedule and a version ID.
|
|
103
117
|
|
|
104
118
|
## Check it
|
|
105
119
|
|
|
@@ -6,6 +6,14 @@ All three are optional, and you can change them at any time. How they appear in
|
|
|
6
6
|
|
|
7
7
|
Write the tagline as one line that says what the newsletter covers and how often it goes out, such as "Monthly notes on the birds of the Hudson Valley." Until your first post, it's the only line on your home page that says what readers are signing up for. It's also what AWS sees if it reviews your site when you [request production access](../get-started/06-production-access.md).
|
|
8
8
|
|
|
9
|
+
## Choose a logo
|
|
10
|
+
|
|
11
|
+
**Use a square PNG, JPEG, WebP, or GIF, at least 256 × 256 pixels, on a solid background.** Drop it on the logo under **Settings → Publication identity**, or choose **Upload**, which reads **Replace** once you have a logo. It saves right away, without **Save changes**. If that would change scheduled emails, Kestrel asks you to confirm first.
|
|
12
|
+
|
|
13
|
+
Kestrel makes a 256 × 256 PNG from your image before uploading it, so the original's file size doesn't matter. A wider or taller image is cropped to its center square, and a smaller one is scaled up and looks soft. An animated GIF keeps its first frame. Emails show the logo at 44 × 44 pixels, so a simple mark reads better than fine detail.
|
|
14
|
+
|
|
15
|
+
A transparent background shows whatever is behind the logo, and the built-in templates turn dark when a reader's mail app is in dark mode. A solid background keeps a dark mark visible there.
|
|
16
|
+
|
|
9
17
|
## When to provide a mailing address
|
|
10
18
|
|
|
11
19
|
**If your newsletter is mostly your own writing, you probably don't need one. If an email is mainly selling something, add one.**
|
|
@@ -6,11 +6,11 @@ Write in Markdown, schedule it, then send yourself a test. Scheduling makes the
|
|
|
6
6
|
|
|
7
7
|
## Write
|
|
8
8
|
|
|
9
|
-
The editor takes Markdown, and Kestrel saves as you type. The same Markdown becomes the email, in your [email template](11-email-template.md), and the post's page in your archive. **Preview** shows the email.
|
|
9
|
+
The editor takes Markdown, and Kestrel saves as you type. The same Markdown becomes the email, in your [email template](11-email-template.md), and the post's page in your archive. **Preview** shows the email, and warns under it about anything it found wrong.
|
|
10
10
|
|
|
11
11
|
- A blank line starts a new paragraph.
|
|
12
12
|
- The opening lines are the preview a mail client shows beside the subject.
|
|
13
|
-
- Paste or drop an image to add it: PNG, JPEG, WebP, or GIF, up to 5 MB.
|
|
13
|
+
- Paste or drop an image to add it: PNG, JPEG, WebP, or GIF, up to 5 MB. A file named for a different format than it really is, like a JPEG called `photo.png`, is refused until you rename it. Kestrel may change its file name, turning spaces into hyphens and numbering a name the post already has, and inserts the new name for you.
|
|
14
14
|
|
|
15
15
|
## Schedule and send
|
|
16
16
|
|
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
# Troubleshooting
|
|
2
2
|
|
|
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
|
|
4
|
-
|
|
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](
|
|
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 and what to do. This page covers what the screen can't: the fixes made outside Kestrel, and the decisions that are yours.
|
|
4
|
+
|
|
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](#notifications), below, lists what you're told.
|
|
6
|
+
|
|
7
|
+
| Symptom | Where the fix is |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| [A send is held or waiting](#a-send-is-held-or-waiting) | Usually outside Kestrel |
|
|
10
|
+
| [Someone was mailed twice, or not at all](#someone-was-mailed-twice-or-not-at-all) | In Kestrel |
|
|
11
|
+
| [A reader says they never got a post](#a-reader-says-they-never-got-a-post) | In Kestrel |
|
|
12
|
+
| [A send still reads Settling](#a-send-still-reads-settling) | Nothing to fix, unless no receipts come at all |
|
|
13
|
+
| [A test doesn't arrive](#a-test-doesnt-arrive) | Outside Kestrel |
|
|
14
|
+
| [Signups aren't open yet, or are paused](#signups-arent-open-yet-or-are-paused) | In Kestrel, or outside it |
|
|
15
|
+
| [The dashboard names a problem with your Amazon SES account](#a-problem-with-amazon-ses) | Outside Kestrel |
|
|
16
|
+
| [An address bounced or complained](#an-address-bounced-or-complained) | Nothing to fix, unless you lift a suppression |
|
|
17
|
+
| [A warning says to move to Workers Paid](../guides/09-as-your-list-grows.md) | Outside Kestrel |
|
|
18
|
+
| [An import is refused](#an-import-is-refused) | In your file |
|
|
19
|
+
| [Claude can't connect](#claude-cant-connect) | Usually outside Kestrel |
|
|
6
20
|
|
|
7
21
|
## A send is held or waiting
|
|
8
22
|
|
|
9
|
-
Nothing is lost. A problem with your email provider never marks anyone unsent or mails anyone twice. The send waits where it is, and carries on once the problem is fixed. The send's line names the fix. When that fix is outside Kestrel,
|
|
23
|
+
Nothing is lost. A problem with your email provider never marks anyone unsent or mails anyone twice. The send waits where it is, and carries on once the problem is fixed. The send's line names the fix. When that fix is outside Kestrel, it's one of these:
|
|
10
24
|
|
|
11
25
|
- **Amazon SES refuses your account or the send.** The line carries SES's words and the fix, almost always in the SES console. [Connect Amazon SES](../get-started/05-ses.md) sets up each piece SES checks: the sending domain, the AWS key, and the configuration set. [Request production access](../get-started/06-production-access.md) lifts the sandbox. When SES refuses your account, any other post that comes due meanwhile waits with it. When it refuses only this send's email, other posts go out as usual. While SES takes some readers and refuses the rest, as while a daily quota frees up, those it takes are sent. The line stays until SES takes everyone.
|
|
12
26
|
- **SES keeps refusing for its sending rate.** Kestrel is sending faster than your SES account allows. **Settings → Email sender** shows the **Send rate** and where it comes from. If `SES_MAX_SEND_RATE` sets it, lower it to the maximum send rate on the SES console's account dashboard, or remove it, then deploy, as [Configuration](../reference/01-configuration.md#vars) describes. If it's Kestrel's default, the app can't read your account: allow its key `ses:GetAccount`, as [Connect Amazon SES](../get-started/05-ses.md#5-create-the-apps-aws-key) shows.
|
|
13
27
|
- **No email provider is connected.** Connect one, as [Connect Amazon SES](../get-started/05-ses.md) describes, and the send goes out at its next retry.
|
|
14
28
|
- **A send missed its time, or nothing has gone out for a while.** The part of Kestrel that sends has stopped, or keeps failing on this send. Check the Worker's Cron Trigger, as [Configuration](../reference/01-configuration.md#bindings-and-triggers) describes, and read the app's logs, as [Read the logs](../get-started/07-verify.md#7-read-the-logs) describes. If another send is going out at the same time, this one may only be waiting its turn.
|
|
15
29
|
|
|
30
|
+
If someone else set up Kestrel for you, pass this along to them.
|
|
31
|
+
|
|
16
32
|
## Someone was mailed twice, or not at all
|
|
17
33
|
|
|
18
34
|
Kestrel never mails a reader twice on its own. When it can't tell whether your provider took some emails, it stops and asks you: the send's page shows **Resolve…**. Choose **Assume not sent** unless you know otherwise, since it's the safe default: those readers may miss this post, but get your next one. Choose **Assume sent** only when you know they got it, such as from a reader who tells you so. Either choice mails no one.
|
|
@@ -27,24 +43,26 @@ Under **Subscribers**, search for their address and open their row. Their page l
|
|
|
27
43
|
|
|
28
44
|
That's normal. **Delivered** counts only the emails your provider confirmed reached the reader's mail server. Confirmations can lag by days, so the counts keep rising after a send finishes. Some are never confirmed at all, so a send can read **Settling** for good, as long as some receipts have come.
|
|
29
45
|
|
|
30
|
-
When none come, the send's page says so an hour after it went out. Your provider's reports aren't reaching Kestrel, so its bounces suppress no one and its complaints unsubscribe no one. Your next post mails those addresses again. Look for `webhook.refused` errors in the app's logs, as [Read the logs](../get-started/07-verify.md#7-read-the-logs) describes. Each names the SNS topic that reached Kestrel and the one it expects. Then check the subscription, as [Connect Amazon SES](../get-started/05-ses.md#7-subscribe-the-app-to-the-topic) describes.
|
|
46
|
+
When none come, the send's page says so an hour after it went out. Your provider's reports aren't reaching Kestrel, so its bounces suppress no one and its complaints unsubscribe no one. Your next post mails those addresses again. Look for `webhook.refused` errors in the app's logs, as [Read the logs](../get-started/07-verify.md#7-read-the-logs) describes. Each names the SNS topic that reached Kestrel and the one it expects. Then check the subscription, as [Connect Amazon SES](../get-started/05-ses.md#7-subscribe-the-app-to-the-topic) describes. If someone else set up Kestrel for you, pass this along to them.
|
|
31
47
|
|
|
32
48
|
## A test doesn't arrive
|
|
33
49
|
|
|
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](
|
|
50
|
+
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](#a-send-is-held-or-waiting). If someone else set up Kestrel for you, pass this along to them.
|
|
35
51
|
|
|
36
52
|
## Signups aren't open yet, or are paused
|
|
37
53
|
|
|
38
54
|
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
55
|
|
|
40
56
|
- "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,
|
|
57
|
+
- "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, as for [a problem with Amazon SES](#a-problem-with-amazon-ses). Then send a test email: once SES takes one, signups reopen.
|
|
42
58
|
|
|
43
59
|
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
60
|
|
|
61
|
+
If someone else set up Kestrel for you, pass this along to them.
|
|
62
|
+
|
|
45
63
|
## A problem with Amazon SES
|
|
46
64
|
|
|
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
|
|
65
|
+
**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:
|
|
48
66
|
|
|
49
67
|
- **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
68
|
- **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.
|
|
@@ -54,11 +72,9 @@ Kestrel notices a refusal when an email meets it, or within about ten minutes fr
|
|
|
54
72
|
|
|
55
73
|
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
74
|
|
|
57
|
-
|
|
75
|
+
If someone else set up Kestrel for you, pass this along to them.
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
## Bounces and complaints
|
|
77
|
+
## An address bounced or complained
|
|
62
78
|
|
|
63
79
|
Kestrel acts on these for you, so a bad address can't keep hurting your sending reputation:
|
|
64
80
|
|
|
@@ -78,31 +94,41 @@ Nothing on your list changes. Kestrel refuses a file it can't read with certaint
|
|
|
78
94
|
|
|
79
95
|
## Claude can't connect
|
|
80
96
|
|
|
81
|
-
Most failures are in your instance's Cloudflare Access setup,
|
|
97
|
+
[Connect Claude](08-connect-claude.md) covers connecting it. Most failures are in your instance's Cloudflare Access setup, and [Allow Claude to connect](../guides/01-allow-claude.md#check-it) lists their fixes. When connecting fails:
|
|
82
98
|
|
|
83
|
-
- **Claude says it couldn't connect, or the sign-in failed.**
|
|
84
|
-
- Signing in shows `invalid_target`
|
|
85
|
-
- `kestrel login`
|
|
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.
|
|
99
|
+
- **Claude says it couldn't connect, or the sign-in failed.** 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. If both are right, your instance's Access setup needs the fix.
|
|
100
|
+
- Signing in shows `invalid_target`, or `kestrel login` says your instance "doesn't offer a sign-in for programs". Your instance's Access setup needs the fix.
|
|
101
|
+
- `kestrel login` or `kestrel api` reports a `401`. Access signed you in, but Kestrel refused the sign-in, and your instance's Access setup needs the fix. If either names `access_unidentified`, it's the "Access let you in" problem below. If `kestrel api` says your sign-in has ended, sign in again as [Connect Claude](08-connect-claude.md#claude-code) describes.
|
|
87
102
|
- `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.
|
|
88
|
-
- **"Access let you in, but didn't say who you are".** Your login carried neither an email nor a service token.
|
|
103
|
+
- **"Access let you in, but didn't say who you are".** Your login carried neither an email nor a service token. Sign out, then sign in through the policy that lets you in by email. If you have no such policy, your instance's Access setup needs the fix.
|
|
89
104
|
- **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.
|
|
105
|
+
- **A change reads "via the Kestrel CLI".** The person it names made it with `kestrel api` outside Claude Code, by hand or through a script, so there's nothing to fix. If Claude Code made it, update Claude Code to v2.1.172 or later: an older one reads this way, and the dashboard can stop saying "Claude is connected."
|
|
106
|
+
|
|
107
|
+
If someone else set up Kestrel for you, pass this along to them.
|
|
108
|
+
|
|
109
|
+
## Where to look
|
|
90
110
|
|
|
91
|
-
|
|
111
|
+
When a line doesn't settle it, these say the most about what happened:
|
|
92
112
|
|
|
93
|
-
|
|
113
|
+
- The top of the dashboard: what needs someone now, in Kestrel's own words.
|
|
114
|
+
- The send's page, under **Sent**: what happened on that send, when it next retries, and its record of each reader.
|
|
115
|
+
- **Settings → Email sender**: the state of your Amazon SES account, and your **Send rate**.
|
|
116
|
+
- **Settings → Notifications**: **Last notification** says whether the last one was delivered, or why not.
|
|
117
|
+
- A subscriber's page, under **Subscribers**: every send they were in, and what happened on each.
|
|
118
|
+
- The app's logs, in your instance's Cloudflare account: [Read the logs](../get-started/07-verify.md#7-read-the-logs) describes them. If someone else set up Kestrel for you, pass this along to them.
|
|
94
119
|
|
|
95
120
|
## Notifications
|
|
96
121
|
|
|
97
122
|
With an address set, Kestrel emails you:
|
|
98
123
|
|
|
99
|
-
- when a send finishes, with how many emails
|
|
100
|
-
- when
|
|
124
|
+
- when a send finishes, with how many emails your provider accepted, and how late it went out if it did
|
|
125
|
+
- when no one's email was accepted for a finished send, since it can't be sent again
|
|
126
|
+
- when email isn't going out and signups are paused: once when it starts, and once more if a post is held after an email that named none
|
|
127
|
+
- when your provider refuses a send or hits a limit, or no provider is connected
|
|
101
128
|
- when the SES sandbox refuses a signup's confirmation, at most once a day
|
|
102
|
-
- when
|
|
103
|
-
- when
|
|
104
|
-
- when a service token imports a list, since no one saw it happen (an import you make yourself, or through Claude, sends nothing)
|
|
129
|
+
- when a send stops going out with nothing to explain it, waits for you to resolve it, or doesn't go out on time
|
|
130
|
+
- when your provider has sent no receipts for six hours after a send, once until a receipt arrives
|
|
105
131
|
|
|
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.
|
|
132
|
+
Each says whether anyone has to act, and how. If the fix needs whoever set up your instance, you can forward it as it stands. Each goes to every address set, links to the page it's about, and is sent once while the problem lasts. A reader's address in your provider's words shows only as its domain, and in full on the send's page. 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.
|
|
107
133
|
|
|
108
134
|
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.
|
|
@@ -26,7 +26,7 @@ A variable is replaced with its value in each email. Type it exactly as shown, o
|
|
|
26
26
|
| `{{ .Publication.LogoURL }}` | Your logo's address, or empty. |
|
|
27
27
|
| `{{ .Publication.Address }}` | Your mailing address, or empty. |
|
|
28
28
|
| `{{ .Email.SentTo }}` | The address this email was sent to. |
|
|
29
|
-
| `{{ .Email.UnsubscribeURL }}` | This reader's
|
|
29
|
+
| `{{ .Email.UnsubscribeURL }}` | This reader's unsubscribe link. Required. |
|
|
30
30
|
| `{{ .Email.ViewInBrowserURL }}` | The post's own web link, which opens its page once it's sent, in your archive or not. |
|
|
31
31
|
|
|
32
32
|
## Test it
|
|
@@ -4,7 +4,7 @@ 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 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.
|
|
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. Setup ends when your first post goes out to the archive, and an email-only send doesn't count.
|
|
8
8
|
|
|
9
9
|
1. Connect Amazon SES
|
|
10
10
|
1. [Send a test email](10-check-your-setup.md)
|
|
@@ -52,14 +52,14 @@ In Cloudflare, set every `CNAME` for DKIM to **DNS only**, not proxied. SES repo
|
|
|
52
52
|
|
|
53
53
|
### DMARC: publish a policy and collect reports
|
|
54
54
|
|
|
55
|
-
DMARC tells an inbox what to do with mail that fails both SPF and DKIM, and
|
|
55
|
+
DMARC tells an inbox what to do with mail that fails both SPF and DKIM, and can ask it to send you reports. It's a `TXT` record at `_dmarc.send.example.com`:
|
|
56
56
|
|
|
57
57
|
```
|
|
58
58
|
_dmarc.send.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
- `p=none` is monitor mode. Inboxes
|
|
62
|
-
- `rua` is where reports go.
|
|
61
|
+
- `p=none` is monitor mode, and it only monitors. Inboxes deliver failing mail as usual, so it doesn't stop anyone sending as your domain. It's where to start because it blocks nothing, your own mail included.
|
|
62
|
+
- `rua` is where reports go. It's optional, and the policy works without it, though [Google's DMARC setup page](https://knowledge.workspace.google.com/admin/security/set-up-dmarc) recommends including it. Reports arrive as attachments, usually one a day from each inbox provider.
|
|
63
63
|
|
|
64
64
|
Leave alignment relaxed, the default, with no `aspf=s` or `adkim=s`. With a custom MAIL FROM domain, your return path is a subdomain of your sending name, so strict SPF alignment would fail.
|
|
65
65
|
|