@kurtbruns/kestrel 1.18.0 → 1.20.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/.dev.vars.example +16 -4
- package/CHANGELOG.md +76 -1
- package/dist/public/dashboard/app.js +289 -290
- package/dist/public/dashboard/app.js.map +4 -4
- package/dist/public/dashboard/index.html +2 -2
- package/dist/public/dashboard/styles.css +127 -21
- package/dist/worker/index.js +4135 -2973
- package/docs/get-started/02-deploy.md +2 -2
- package/docs/get-started/03-access.md +17 -5
- package/docs/get-started/04-aws-account.md +2 -0
- package/docs/get-started/05-ses.md +49 -28
- package/docs/get-started/06-production-access.md +6 -3
- package/docs/get-started/07-verify.md +9 -7
- package/docs/get-started/08-go-live.md +12 -16
- package/docs/guides/01-allow-claude.md +7 -2
- package/docs/guides/03-notifications.md +10 -10
- package/docs/publishing/04-write-a-post.md +2 -2
- package/docs/publishing/06-troubleshooting.md +70 -21
- package/docs/publishing/08-connect-claude.md +1 -1
- package/docs/publishing/09-share-your-subscribe-link.md +3 -1
- package/docs/publishing/10-check-your-setup.md +13 -9
- package/docs/publishing/11-email-template.md +1 -1
- package/docs/publishing/README.md +8 -3
- package/docs/reference/01-configuration.md +5 -5
- package/migrations/0016_held_receipts.sql +26 -0
- package/migrations/0017_provider_account_rejected.sql +24 -0
- package/migrations/0018_notify_addresses.sql +13 -0
- package/migrations/0019_sending.sql +67 -0
- package/migrations/0020_notifications_not_sending.sql +40 -0
- package/migrations/0021_notifications_outage_receipts.sql +46 -0
- package/package.json +1 -1
- package/packages/kestrel/lib/api.mjs +28 -3
- package/packages/kestrel/lib/oauth.mjs +5 -3
- package/template/claude/CLAUDE.md +4 -2
- package/template/wrangler.jsonc +5 -5
package/.dev.vars.example
CHANGED
|
@@ -27,8 +27,8 @@ MIN_LEAD_SECONDS="60"
|
|
|
27
27
|
# On a list send, local dev stands in for a real provider instead of accepting everything
|
|
28
28
|
# at once: the provider's batch size and pace, its failures, and delivery receipts that
|
|
29
29
|
# arrive after the send (delivered, bounced, complained), all through the real send loop.
|
|
30
|
-
# Test sends and
|
|
31
|
-
#
|
|
30
|
+
# Test sends, confirmations, and notifications go straight to the dev outbox
|
|
31
|
+
# (GET /api/dev/outbox), unfaulted. The profile names the provider modeled:
|
|
32
32
|
# ses Amazon SES: a second of its send rate a request (up to 50), one request a
|
|
33
33
|
# second, no key (a lost request waits for Resolve), and its daily quota running
|
|
34
34
|
# out once a send; a slice a minute on the default SUBREQUEST_BUDGET, as SES is
|
|
@@ -38,6 +38,12 @@ MIN_LEAD_SECONDS="60"
|
|
|
38
38
|
# plain instant fake. "not-connected" runs the app as a deployment with no email provider
|
|
39
39
|
# yet: every send refused, signups closed, a due send held. Switched back, the held send
|
|
40
40
|
# goes out at its next retry, a few minutes later.
|
|
41
|
+
# Add ":refuse=" and a cause (e.g. "ses:refuse=configuration") to have SES refuse every
|
|
42
|
+
# email, tests, confirmations, and notifications included, as it would for that cause:
|
|
43
|
+
# credentials, opt_in, configuration, sender, mail_from, paused, set_paused, suspended, or
|
|
44
|
+
# unsupported. Email then reads as refusing and signups as paused. Beside a sandbox account
|
|
45
|
+
# (SIMULATE_ACCOUNT=sandbox), SES refuses any recipient not at SENDING_DOMAIN or SES's
|
|
46
|
+
# mailbox simulator.
|
|
41
47
|
# For one run, set it in the shell instead: SIMULATE_SENDS=ses npm run dev
|
|
42
48
|
# Only honored in a dev-shaped env (fake transport, no Access, APP_ORIGIN on localhost).
|
|
43
49
|
SIMULATE_SENDS="ses"
|
|
@@ -47,7 +53,13 @@ SIMULATE_SENDS="ses"
|
|
|
47
53
|
# real account would: sandbox, pending (AWS reviewing a request for production access),
|
|
48
54
|
# denied, production, or permission (the AWS key isn't allowed to read the account). Add
|
|
49
55
|
# ":unrouted" (e.g. "sandbox:unrouted") for bounces that reach no configuration set, which
|
|
50
|
-
# shows the bounces warning
|
|
51
|
-
#
|
|
56
|
+
# shows the bounces warning, or one problem the account check finds, which shows its warning:
|
|
57
|
+
# ":set_missing" or ":set_paused" (the configuration set), ":key_rejected" (SES rejects the
|
|
58
|
+
# AWS key), ":sending_disabled" (sending turned off), ":shutdown" (AWS paused the account),
|
|
59
|
+
# ":sender_unverified" (the From address isn't verified), or ":probation" (AWS reviewing the
|
|
60
|
+
# account), e.g. "production:set_missing". A problem that stops every email also refuses every
|
|
61
|
+
# simulated email with its cause (set_missing as configuration, key_rejected as credentials, and
|
|
62
|
+
# so on), as the real account would, unless SIMULATE_SENDS names a refusal of its own. In the sandbox, a recipient outside SENDING_DOMAIN
|
|
63
|
+
# and SES's mailbox simulator is refused. Needs SIMULATE_SENDS=ses; "off" leaves the account unread.
|
|
52
64
|
# For one run, set it in the shell instead: SIMULATE_ACCOUNT=sandbox npm run dev
|
|
53
65
|
SIMULATE_ACCOUNT="off"
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,79 @@ 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.20.0] - 2026-11-04
|
|
14
|
+
|
|
15
|
+
**Upgrading from 1.19.0.** In your instance's repository, run `npm run sync-migrations`, which brings in one database migration, `0021_notifications_outage_receipts`: it rebuilds the notifications table for the new notices, carrying every row over, and closes any import notice not yet sent, since this release sends none. Run `npm run check-context`: this release changes the operator's Claude context, and a copy you've edited is merged by hand. Apply the migration before you deploy, with `npx wrangler d1 migrations apply DB --remote --env production` (and `--local` for local development). Then deploy with `npx wrangler deploy --env production`. A script of yours that replaced an image by uploading its name again needs changing (see Breaking); nothing else does.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Kestrel emails you when your provider has sent no receipts for six hours after a send, once until a receipt arrives, since a broken connection stops bounces and complaints from suppressing anyone (SPEC §8).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Notification emails are rewritten to say whether anyone has to act, and how, with the details left to the dashboard: Sent is a line or two and a link, every problem reads in one order, links are named for what you'd do there, and each email says why you got it (SPEC §8).
|
|
24
|
+
- One outage now sends one email instead of a per-day notice beside a per-post one, naming the posts it holds, plus one more if a post is first held after an email that named none (SPEC §8). The notification status's `kind` gains `no_receipts` and `not_sending_held`.
|
|
25
|
+
- A send that goes out late says so in its Sent email instead of a separate one, and a finished send no one accepted is now told as a problem, since it can't be sent again (SPEC §8).
|
|
26
|
+
- A reader's address in your provider's words shows only as its domain in an email, and in full on the send's page (SPEC §12).
|
|
27
|
+
- The fix for a provider refusal, in the editor, the API's `advice` and `message`, and email, now says what to do and adds "If someone else set up Kestrel for you, pass this along to them." instead of "Whoever runs the deployment" (SPEC §12).
|
|
28
|
+
- Kestrel no longer emails you when a service token imports a list. The import stays on the record with who committed it (SPEC §7).
|
|
29
|
+
- The setup guide's checks now say what to do about the failures a real setup ran into, in a sentence beside the check, and Troubleshooting is a publisher's page again: it opens with a table of symptoms and where each fix is, and a section on where to look. Its section on Claude's Access setup moved to Allow Claude to connect, the one on Cloudflare refusing to save the Access application to Lock the dashboard with Access, and the one on outgrowing Workers Free now points to As your list grows.
|
|
30
|
+
- The dashboard's **Finish setting up** card now says under its title that it stays until your first post goes out to the archive and that email-only sends don't count, and **Share your subscribe link** says to share it before then, so readers get that post.
|
|
31
|
+
- A template test email now says what it is, under the subject "Template test: how your posts will look", instead of arriving as a made-up post about starlings under your own sign-off. Settings' previews of the reader pages show the same sample; the Template page's preview keeps its placeholder text.
|
|
32
|
+
- The setup guide's Go live page is now a checkpoint on where to go next: one path if you're writing the newsletter, one if someone else is, and the optional guides, each named by when you'd want it. How to let someone else into the editor moves to its own section of Lock the dashboard with Access.
|
|
33
|
+
- The operator's Claude context (`template/claude/CLAUDE.md`) now says to reference an image by the `filename` the upload answers, since it may differ from the name sent, says how to swap an image, and names the preview's new warnings.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- An image uploaded with spaces in its name, as every macOS screenshot has, or with other characters that break a Markdown reference, now shows in the email instead of its `` text: the upload stores it with hyphens in their place and answers that `filename` to reference, and images already stored keep their names. The preview now warns when text that reads as an image reference didn't become an image, and when an image uploaded to the post isn't used by it (SPEC §4), and the editor's Preview tab now shows a render's warnings, which it used to show only after a test.
|
|
38
|
+
- Two images pasted into one post no longer become one image: a browser names every pasted image the same, so the second replaced the first, both references showed it, and the preview could show a stale copy.
|
|
39
|
+
|
|
40
|
+
### Breaking
|
|
41
|
+
|
|
42
|
+
- An upload no longer replaces an image of the same name. A name the post already has gets a number, so a second `cover.jpg` is stored as `cover-2.jpg`, and the response's `filename` says which (SPEC §4). A client that replaced an image by uploading its name again now uploads the new one, references its `filename`, and deletes the old one.
|
|
43
|
+
|
|
44
|
+
## [1.19.0] - 2026-11-02
|
|
45
|
+
|
|
46
|
+
**Upgrading from 1.18.0.** If your instance sends through Amazon SES, let the app's AWS key send templated email now, whether or not you upgrade: in the IAM console, open **Policies**, then `kestrel-send-email`, choose **Edit**, add `"ses:SendTemplatedEmail"` and `"ses:SendBulkTemplatedEmail"` to its `Action` list in the JSON editor, and save. Keep the actions already there. A key with the guide's earlier policy can't send at all: until you add them, SES refuses every email the app sends as not authorized, test emails included. While you're there, add `"ses:GetConfigurationSet"` too, so the app can tell whether the configuration set exists and can send. Without it the app says nothing about the set, and is never wrong about it. In your instance's repository, run `npm run sync-migrations`, which brings in five database migrations: `0016_held_receipts`, a new table for receipts SES reports before the app has recorded the message they name, which rewrites nothing; `0017_provider_account_rejected`, which rebuilds the one-row table where the app keeps what it reads of your SES account, so it can record a key SES rejects, carrying its row over; `0018_notify_addresses`, which records, for each notification, the addresses it has reached, and rewrites nothing; `0019_sending`, a one-row table recording whether email is getting out, which starts as working when your instance's records show email went out (a post SES took a recipient of, or a subscriber who confirmed through a link it mailed), so a live instance keeps its signups open; and `0020_notifications_not_sending`, which rebuilds the notifications table for the new notice that email isn't going out, carrying every row over. Your saved notifications address carries over as a list of that one address. Run `npm run check-context`: this release changes the operator's Claude context, and a copy you've edited is merged by hand. Apply the migrations before you deploy, with `npx wrangler d1 migrations apply DB --remote --env production` (and `--local` for local development). Then deploy with `npx wrangler deploy --env production`. Then open **Settings → Email sender**. If it shows **Not proven yet**, as it does when your records show no email went out beyond tests, signups stay closed until SES takes an email: send one test email from **Template → Send test email** to open them. Within about ten minutes the app also checks your SES account, and a red warning there now pauses signups until the check after the fix. A script of yours that read a subscriber's `confirm_token` or `unsub_token` from the API, or read or wrote `notifications.to` in the settings, needs changing (see Breaking); nothing else does.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- On Amazon SES, the account check now finds, without sending anything, the setup mistakes that used to show only when a send, a test, or a reader's signup was refused (SPEC §13): a configuration set that doesn't exist in the region or has sending turned off, an AWS key SES rejects (an unknown key, a secret that doesn't match, or an account not signed up for SES, which used to read as "can't tell"), sending turned off for the account or paused by AWS, and a From address with no verified identity. It also shows when AWS is reviewing the account. Each is a warning in Settings → Email sender and the dashboard, red when no email can go out and amber otherwise, and `GET /api/provider` carries them with a new `error` severity and `unknown_reason` `rejected`. The check reads the sending identity on every check, not only when `SES_CONFIGURATION_SET` is unset. Connect Amazon SES's key policy adds `ses:GetConfigurationSet`, and local development can show each finding with `SIMULATE_ACCOUNT`, such as `production:set_missing`.
|
|
51
|
+
- Notifications can go to up to five addresses, added one at a time under Settings → Notifications as the default test recipients are, so whoever runs an instance for someone else is told of problems alongside them. Each address gets every notification, an address that fails is tried again without resending to the others, and **Send a test notification** goes to each (SPEC §8). While SES is in the sandbox, each address must be verified in SES, or with Cloudflare's email when notifications go through it.
|
|
52
|
+
- Kestrel now records whether email is getting out: not proven yet, working, or refusing when SES refuses the account, found by any email or by the account check (SPEC §13). `GET /api/provider` carries it as `sending` and `signups`, and Claude's `get_publication` carries both, with a new `send_template_test` tool to prove email works.
|
|
53
|
+
- While SES refuses the account, the dashboard shows a red line with SES's words and the fix, Subscribers a red line saying signups are paused, and Settings → Email sender a **Refusing** pill, each ending in sending a test email; before SES has taken any email, Settings shows **Not proven yet** instead. Add subscriber and a test email are refused with SES's cause and the fix. When a reader's signup or the account check finds the refusal, with no one there to see it, the publisher is emailed at most once a day (SPEC §8).
|
|
54
|
+
- A send scheduled while SES is in the sandbox warns that it reaches only verified addresses until AWS grants production access (SPEC §6).
|
|
55
|
+
- Local development can have SES refuse every email with `SIMULATE_SENDS=ses:refuse=<cause>`, and a simulated account problem or sandbox refuses as the real one would (SPEC §10).
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- Settings → Reader pages shows the landing and archive pages as readers get them once signups are open, whatever the instance's state, so a publisher previewing mid-setup sees the Subscribe link and the empty landing page's form. Each page now has its own line, beside the preview and as its row's tooltip, saying how a reader comes to it, in place of one note per journey.
|
|
60
|
+
- The subscribe form takes signups only while email is working, in the SES sandbox as well as with production access, and otherwise says "Signups aren't open yet" or "Signups are paused" to every address alike, with a new `signups_closed` (503) for JSON (SPEC §7). The home page and the archive leave out their form and Subscribe link while signups are closed. A reader is no longer told a link is coming when none can go.
|
|
61
|
+
- The operator's Claude context (`template/claude/CLAUDE.md`) and the MCP server's instructions read an instance's setup as the editor does. They know the ten setup steps and the fact that ticks each, so Claude answers "what's left?" by them. They raise the account check's `error` warnings before its `warn` ones, read an unknown state with `unknown_reason` `rejected` as a key SES rejects rather than a question about production access, and read `review: "denied"` as AWS having replied, so Claude never tells a publisher they were turned down; the sandbox rule also lists `ses:GetConfigurationSet`, and `get_publication` carries each warning's severity and the unknown reason. They offer the subscribe link only while signups are open and SES has production access, and say to send a template test once a refusal is fixed, so Claude never shares a closed form.
|
|
62
|
+
- The dashboard's setup checklist is ten steps in one list, each checked off only from what Claude can read too: Connect Amazon SES, Send a test email (once SES has taken an email), Name your publication, Get notified, Request production access, Bring your existing list, Write your first post, Schedule your first post, Send a test email for your first post (a test of the scheduled email, not of the draft), and Share your subscribe link. Finished steps hide behind one "N done" toggle that shows them in place, a step that can't be taken yet says which step it waits for, and problems stay in the health block, which first run now shows too. A notification that didn't arrive is now a health line too. Check your setup and the Publishing docs' setup list follow the same steps (SPEC §13).
|
|
63
|
+
- An SES bad request that names a configuration set, as a deleted default set causes, now refuses the account's configuration instead of failing one recipient for good.
|
|
64
|
+
- Lock the dashboard with Access says how to let another person into the editor: add them to the Access policy, and store `ACCESS_ALLOWED_EMAILS` again with their address if you set it, since storing it replaces the whole list. It and the Configuration reference show the list's format, and say spaces and capital letters don't matter.
|
|
65
|
+
- While AWS hasn't granted production access and has replied in the support case, Settings and the dashboard say so, with the case number, and to answer there, instead of "AWS denied production access". AWS's account reads the same for the request for more information it sends within seconds of every request as for a refusal. The wire value `review: "denied"` is unchanged, and its reference description reads it the same way.
|
|
66
|
+
- Request production access says AWS's automated request for more information comes every time, and adds a sample email to what to send AWS.
|
|
67
|
+
- SES refusing a request as expired, which a clock running more than 15 minutes off causes, now waits for the retry as SES being unavailable, instead of holding the send as a refusal of the account's credentials (SPEC §12).
|
|
68
|
+
- Connect Amazon SES follows the SES, SNS, and IAM consoles as they are now: it creates the key's policy before its user, sets SNS's longer retry policy on the subscription form, and leaves alone the SES statement a topic created from SES already has. It says why the other six event types stay off, and ends its replies section with a check in Email Routing's Activity log.
|
|
69
|
+
- While Amazon SES is in the sandbox, an empty Subscribers list offers only the import, says to share the subscribe page once AWS grants production access, and links Request production access. A reader who signs up before then gets no confirmation email, since SES mails only addresses verified in it.
|
|
70
|
+
- The confirm page's line under its heading reads "Confirm reader@example.com to start receiving the newsletter.", in the reader's words like the confirmation email, instead of asking whether to start sending to them.
|
|
71
|
+
- The comment `kestrel init` writes above `PROVIDER` in `wrangler.jsonc` stays true after Connect Amazon SES sets it to `ses`. Only new instances get it; an existing instance can edit its comment by hand.
|
|
72
|
+
- Allow Claude to connect's check after turning on Managed OAuth states only the expected answer, and says Access can take a few minutes more to accept sign-ins, showing `invalid_target` until then. Troubleshooting adds the `invalid_target` case, and Connect Claude includes the **Connect** and **Allow** steps after **Add**.
|
|
73
|
+
- `kestrel api` run without `--url` or `KESTREL_URL` names the instance it calls on stderr ("calling …, the only instance you're signed in to"), so a call from another instance's folder no longer goes to that sign-in unseen.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- A bounce, complaint, or delivery SES reports in the seconds between taking a batch and the app recording SES's answer now lands on the post's record within a minute, instead of being left off it, so the record no longer reads as delivered for that reader. The address was suppressed either way. One for a recipient left awaiting Resolve lands if you resolve them as sent. Each wait is logged as `held` on `receipt.applied`, and its landing as `receipt.released` (SPEC §10).
|
|
78
|
+
- Connect Amazon SES's IAM policy, `kestrel-send-email`, now includes `ses:SendTemplatedEmail` and `ses:SendBulkTemplatedEmail`. Every SES email has been sent as an inline template since 1.8.0, so a key with only the guide's earlier actions had every email refused, test emails included. The advice for an SES credentials refusal names all four send actions.
|
|
79
|
+
- When renewing a sign-in gets a 404 from its Access team, `kestrel api` says so, naming the instance and the team, and to run `npx kestrel login` again, instead of "Access refused the token request (404)". A real refusal still reads "Access refused the token request", now naming the instance.
|
|
80
|
+
|
|
81
|
+
### Breaking
|
|
82
|
+
|
|
83
|
+
- A subscriber no longer carries `confirm_token` or `unsub_token` in any API answer, so no API client, Claude included, can confirm a pending address or unsubscribe someone as if the reader had (SPEC I1). Both stay in the database, and the links in mail work as before. Read `status` and `confirm_sent_at` for where a confirmation stands, and add the address again (`POST /api/subscribers`) to send another.
|
|
84
|
+
- `notifications.to` in the settings is a list of addresses, no longer one address: `GET /api/settings` answers `["you@example.com"]` where it answered `"you@example.com"`, and `[]` where it answered `""`, and `PUT /api/settings` takes a list and refuses a string. `POST /api/settings/notifications/test` answers `to` as that list, with `failed` naming any address the channel refused, and is `notify_failed` only when every address was refused.
|
|
85
|
+
|
|
13
86
|
## [1.18.0] - 2026-10-26
|
|
14
87
|
|
|
15
88
|
**Upgrading from 1.17.0.** In your instance's repository, run `npm run sync-migrations`, which brings in one database migration, `0015_provider_account`: a new table where the app keeps what it reads of your SES account, which rewrites nothing. Apply it before you deploy, with `npx wrangler d1 migrations apply DB --remote --env production` (and `--local` for local development). If your instance already sends through Amazon SES, let the app's AWS key read the account before you deploy: in the IAM console, open **Policies**, then `kestrel-send-email`, choose **Edit**, add `"ses:GetAccount"` and `"ses:GetEmailIdentity"` to its `Action` list in the JSON editor, and save. Otherwise the app's first check finds the key not allowed, Settings says so with **Add the permission**, and the app asks again only hourly. An instance not yet connected to SES skips this, since Connect Amazon SES's policy now includes both. Then deploy with `npx wrangler deploy --env production`. `SES_MAX_SEND_RATE` is now a cap: a send keeps to the lower of it and your account's own rate, so the old guide's `"1"` would hold every send at one a second even after AWS grants production access. Once Settings → Email sender shows a **Sandbox** or **Production** badge beside Amazon SES, the app has read your account: remove `SES_MAX_SEND_RATE` from the `production` block's `vars` in `wrangler.jsonc`, or raise it to the most you want, and deploy again. Run `npm run check-context`: this release changes the operator's Claude context, and a copy you've edited is merged by hand. An existing instance keeps its Worker, database, and bucket names: don't rename them, and don't copy a new instance's names from the template into your `wrangler.jsonc`. If you're partway through the setup guide, continue from its renumbered pages: 1.17.0's Connect Amazon SES section 4 is now its own page, Request production access, after Connect Amazon SES.
|
|
@@ -689,7 +762,9 @@ The first tagged release: a self-contained newsletter app on a Cloudflare Worker
|
|
|
689
762
|
- A build-version stamp: every instance reports its version, commit, and build time, read-only in the editor and at `GET /api/version`.
|
|
690
763
|
- This changelog and a semantic-versioning release process.
|
|
691
764
|
|
|
692
|
-
[Unreleased]: https://github.com/kurtbruns/kestrel/compare/v1.
|
|
765
|
+
[Unreleased]: https://github.com/kurtbruns/kestrel/compare/v1.20.0...HEAD
|
|
766
|
+
[1.20.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.20.0
|
|
767
|
+
[1.19.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.19.0
|
|
693
768
|
[1.18.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.18.0
|
|
694
769
|
[1.17.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.17.0
|
|
695
770
|
[1.16.0]: https://github.com/kurtbruns/kestrel/releases/tag/v1.16.0
|