@kurtbruns/kestrel 1.8.0 → 1.9.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 +22 -1
- package/README.md +1 -1
- package/dist/public/dashboard/app.js +148 -148
- package/dist/public/dashboard/app.js.map +3 -3
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +23 -0
- package/dist/worker/index.js +2197 -480
- package/docs/README.md +1 -0
- package/docs/get-started/04-ses.md +1 -1
- package/docs/get-started/06-go-live.md +5 -2
- package/docs/reference/01-configuration.md +7 -1
- package/docs/reference/03-import-format.md +103 -0
- package/migrations/0005_imports.sql +74 -0
- package/migrations/0006_deliveries_fewer_indexes.sql +19 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,26 @@ To move a running instance from one version to another, follow [Upgrade to a new
|
|
|
10
10
|
|
|
11
11
|
<!-- Add entries under Added / Changed / Fixed / Breaking. One operator-facing line each; see .claude/rules/changelog.md. Upgrade steps (a migration, a new setting) go once, in the Upgrading paragraph, never on an entry. -->
|
|
12
12
|
|
|
13
|
+
## [1.9.0] - 2026-10-13
|
|
14
|
+
|
|
15
|
+
**Upgrading from 1.8.0.** Two database migrations: run `npm run sync-migrations`, then `npx wrangler d1 migrations apply DB --remote --env production` (and `--local` for local development) before deploying. The first (`0005_imports`) adds the record of each import and how each subscriber joined, reading every existing subscriber as joined through Kestrel's double opt-in, and rebuilds the notifications table so an import can be told to you. The second (`0006_deliveries_fewer_indexes`) drops two indexes no query reads; it writes no rows, so it costs nothing against D1's daily allowance at any size.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Bring an existing list in, and take the whole list out, through the API, in one documented format, `kestrel-subscribers/1` (a CSV, or a plain `.txt` list of addresses). A plan shows what the import would do to every address before anything changes; the commit applies it whole, with your attestation that everyone imported as subscribed opted in, and imports them as confirmed with how and where they joined, never reversing an unsubscribe (even for a reader the subscribe form has put back to pending) or lifting a suppression here, and sending no email. Each subscriber now records how they joined beside their state; Claude may attest for you, and you're emailed when it does, but only a person can let an import grow a scheduled send. The export reads the list out in the same format, a page at a time (1,000 rows on the Workers Free plan's limits, 10,000 otherwise), with every address on the list for the whole export appearing exactly once, so another instance imports it back; in the CSV written from its pages, a value a spreadsheet would run as a formula is written as text. One import takes up to 10,000 rows, or 1,000 on the Workers Free plan's limits (`SUBREQUEST_BUDGET` unset). The format's reference is in the Docs tab and at `GET /api/imports/format` (SPEC §7, §8).
|
|
20
|
+
- On the Workers Free plan's limits (`SUBREQUEST_BUDGET` unset), where D1 refuses every query once a day's 100,000 written rows are spent and the instance stops until midnight UTC, the app now warns before it gets there. A send to more than 5,000 recipients says so in the Schedule dialog, on the scheduled post, and to Claude as the send's `daily_writes` condition. An import plan that would leave more than 3,000 confirmed subscribers advises sending the next day (UTC). Neither warning ever refuses. The setup guide now recommends Workers Paid from about 5,000 subscribers, for the database's writes as well as send speed (SPEC §7, §8).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- A send uses fewer of D1's rows: about 14 written and 41 read per recipient, receipts included, down from about 16 and 118. On the Workers Free plan, whose D1 refuses every query once a day's 100,000 written rows are spent, one send can now reach about 7,000 subscribers in a day instead of about 6,200. On Workers Paid, a send bills fewer writes and leaves a smaller database.
|
|
25
|
+
- The API's refusals and render warnings that the editor shows read as sentences that name the next step, with typographic apostrophes and no em dashes: a save or image upload on a scheduled post, a test with no recipients or too many, an address Settings won't take, a scheduling or Resolve refusal, a warning such as an image with no alt text. A template test's refusals about its recipients now name `to` as their `field`, and a save, delete, or image change on a post that isn't a draft is refused as 409 `post_not_draft` instead of `conflict`, saying whether canceling the schedule would unlock it or its send has already started.
|
|
26
|
+
- A send time inside the minimum lead is refused in one plain sentence, "Pick a time at least 5 minutes from now, so there’s time to cancel.", which is now the API's own message on scheduling and rescheduling alike, so Claude reads what the publisher reads where it used to read the route to call; Send now remains the soonest send (SPEC §6).
|
|
27
|
+
- The Schedule dialog's Send now row reads "Or send as soon as possible.", and the Send now view says when the send goes out (one lead, up to the next minute), that the post locks until then, and that it can be canceled before it starts sending, counting recipients as the scheduled card does rather than "confirmed subscribers". The Schedule and Reschedule hints no longer name the lead, which the refusal states when a time is too soon, and the post's scheduled banner drops "cancelable until then", which its Cancel button and countdown already say.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- A list send whose `SES_CONFIGURATION_SET` names a configuration set SES can't find in its region is held with nobody consumed, as a new `configuration` cause whose advice names the setting, where before SES's `NotFoundException` marked every recipient unsent, and its per-recipient form advised replacing the access key (SPEC §12).
|
|
32
|
+
|
|
13
33
|
## [1.8.0] - 2026-10-12
|
|
14
34
|
|
|
15
35
|
**Upgrading from 1.7.0.** Do these in order. First, if your `production` block sets `PROVIDER` to `resend`, switch it to SES on the release you run now, while no send is sending, as [Connect Amazon SES](docs/get-started/04-ses.md#switch-an-instance-that-already-sends) describes, and upgrade only after that: this release refuses `resend`, and until the switch the public pages say the newsletter is temporarily unavailable and nothing is sent. From the switch on, the app no longer records the bounces and complaints Resend reports, so if you can, let a few days pass after your last Resend send before you switch. Then delete the `RESEND_API_KEY` and `RESEND_WEBHOOK_SECRET` secrets and the Resend webhook. Second, if you send through SES, make the `Action` of your sending key's IAM policy (Connect Amazon SES's `kestrel-send-email`) `["ses:SendEmail", "ses:SendBulkEmail"]` before you deploy, where 1.7.0's guide gave only `ses:SendEmail`: this release sends through SES's bulk API, and until the key may call it every list send is held as SES refusing the credentials, while a test email still goes out, so a test won't show it's missing. You may also add **Rendering failures** to your configuration set's event destination, so the app can log one. Third, before you commit the upgrade, as they apply to you: if your `production` block sets `PROVIDER` to `fake` on a real hostname, this release refuses it: set your email provider, or make it `""` until you [connect one](docs/get-started/04-ses.md). A hosted demo that runs `fake` on a real hostname on purpose adds `ALLOW_FAKE_PROVIDER: "true"` before it upgrades, to the env it hands Kestrel: its `vars`, or the object it builds if it builds one, and that object's type. If your `production` block has no `PROVIDER` line, you may add `"PROVIDER": ""` to its `vars` to quiet wrangler's warning that it's missing there; it means the same as leaving it out. If sign-in to a deployed instance has been failing, store `ACCESS_TEAM_DOMAIN` again as the bare hostname (like `yourteam.cloudflareaccess.com`): a value this release still can't read as a hostname, such as one with a path or a port, is now refused, and until it's fixed the public pages say the newsletter is temporarily unavailable and nothing is sent, where before only sign-in failed. A local `.dev.vars` with `SIMULATE_SENDS` set to `resend` (or `resend:none`) changes it to `ses` (or `ses:none`). Fourth, one database migration, which `npm run sync-migrations` brings in before you commit the upgrade: apply it after noting a restore point and before you deploy, as [Upgrade to a new release](docs/guides/07-upgrade.md) does, with `npx wrangler d1 migrations apply DB --remote --env production` (and `--local` for local development). It moves any recipients a send left queued to be re-sent under an earlier provider's key into flight, where **Resolve** settles them, rather than leaving the send open and silent; on a database with none it changes nothing.
|
|
@@ -359,7 +379,8 @@ The first tagged release: a self-contained newsletter app on a Cloudflare Worker
|
|
|
359
379
|
- A build-version stamp: every instance reports its version, commit, and build time, read-only in the editor and at `GET /api/version`.
|
|
360
380
|
- This changelog and a semantic-versioning release process.
|
|
361
381
|
|
|
362
|
-
[Unreleased]: https://github.com/kurtbruns/kestrel/compare/v1.
|
|
382
|
+
[Unreleased]: https://github.com/kurtbruns/kestrel/compare/v1.9.0...HEAD
|
|
383
|
+
[1.9.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.9.0
|
|
363
384
|
[1.8.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.8.0
|
|
364
385
|
[1.7.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.7.0
|
|
365
386
|
[1.6.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.6.0
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Kestrel
|
|
2
2
|
|
|
3
|
-
Kestrel is a newsletter app you run yourself on Cloudflare. You write a post in Markdown, preview it exactly as the email, and send it to a double-opt-in list. The list, the consent, the record of every send, and a permanent archive of every post stay with you. Write in the editor by hand, or with Claude through the same API.
|
|
3
|
+
Kestrel is a newsletter app you run yourself on Cloudflare. You write a post in Markdown, preview it exactly as the email, and send it to a double-opt-in list. The list, the consent, the record of every send, and a permanent archive of every post stay with you. Bring an existing list in, and take the whole list out, in one documented format. Write in the editor by hand, or with Claude through the same API.
|
|
4
4
|
|
|
5
5
|
See [getkestrel.dev](https://getkestrel.dev) to learn more about Kestrel.
|
|
6
6
|
|