Skip to content
Sendoff
Esc
↑↓navigate↵open⌘Jpreview
On this page

Updating and running

Ship new versions, keep your instance's files safe, and run Sendoff locally.

Your copy of Sendoff is a git clone of the repo, made by bun create sendoff, and your settings live in its database, not in code. So updating is just moving to a newer version and redeploying.

Updating

From your Sendoff folder:

bun run update

It fetches Sendoff’s releases and shows the release notes for every version since yours. If you say yes, it moves your folder to the latest release, installs dependencies and runs bun run deploy. That applies any new database migrations first, then deploys the Worker with your instance’s config, and the Pages project in front of it if your DNS isn’t on Cloudflare. It doesn’t touch your secrets, DNS or SES setup.

Type the run: bun update on its own is Bun’s command for updating dependencies.

If your folder has uncommitted changes, or commits of its own, it stops without changing anything and says what to do. If your folder doesn’t have bun run update yet, run git pull once to get it.

Running the latest code

To run what’s on main, including changes that aren’t released yet:

git pull
bun install
bun run deploy

Unreleased changes don’t have release notes yet, so bun run update is the safer way to update.

What’s changed

Each version of the Worker has release notes on GitHub, under Releases (the ones tagged app@), and in apps/app/CHANGELOG.md in your folder. bun run update shows you the ones you’re about to get. If a version needs more than deploying, like running setup again, its notes say so.

To hear about new versions, open the Sendoff repo on GitHub and choose Watch › Custom › Releases.

Your instance’s files

Setup records your instance in apps/app/.sendoff/, which git ignores:

  • instance.json: your Cloudflare account, hostname, and the IDs of the database, KV namespace, queue, Turnstile widget, SES resources and Pages project (if you have one)
  • wrangler.jsonc: the deploy config generated from it
  • pages/: the Pages project’s code and config, generated from it, if you have one

bun run deploy needs them. If you lose them, or move to another machine, run bun run setup again. It finds the database, queue and SES resources that already exist instead of creating new ones, and keeps the Worker’s TOKEN_SECRET. It can’t look up the Turnstile widget or the Worker’s AWS key without these files, though, so it makes new ones. Delete the old widget in the Cloudflare dashboard afterwards. Setup lists the sendoff IAM user’s old key, with when it was last used, for you to delete: IAM allows a user only two keys, so setup stops if both are taken. It doesn’t delete the key itself, since another Sendoff instance in the same AWS account could be using it.

The repo’s own apps/app/wrangler.jsonc stays generic, so pulling updates never conflicts with your instance.

Running setup again

Setup is safe to re-run at any time. It checks every step and skips what’s already done. Run it to:

  • Finish an interrupted setup, such as when DNS took too long
  • Replace the MCP token, by choosing create a new one when it asks
  • Recreate the Worker’s AWS key, if you deleted it
  • Turn on delivery reports, when doctor warns that SES doesn’t send them (instances set up before Sendoff counted deliveries)

It never rotates TOKEN_SECRET, which signs the unsubscribe and tracked links in every email you’ve ever sent. Changing it would break all of them.

Running locally

cd apps/app
cp .dev.vars.example .dev.vars
bun run db:migrate
bun run dev

That serves the Worker at http://localhost:8787 with a local database and Turnstile’s test keys. Fill in real AWS keys in .dev.vars to send test emails. Open /preview/sample to see the layout with sample content.

Other commands, from the repo root:

Command What it does
bun run test Runs the tests inside workerd, the Workers runtime
bun run types Typechecks
bun run check Lints and checks formatting
cd apps/app && bun run bench Measures render CPU time against the Workers limit

Last updated on

Was this page helpful?