Use this runbook for the voting Worker and its D1 database. Read Voting system before changing ballot behavior or token handling.

Runtime

PartCurrent implementation
ApplicationCloudflare Worker at src/index.ts, with routes under src/routes/
Static filespublic/, served through the Worker assets binding
Primary dataD1 database bound as DB
Token coordinationTokenManager Durable Object bound as TOKEN_MANAGER
Scheduled workCron trigger every minute
Outbound emailResend calls from src/utils/email.ts
ObservabilityWorker observability enabled in wrangler.jsonc

Local development

From the ihnyc-rc-vote repository:

npm ci
cp .dev.vars.example .dev.vars
npm run dev

Add local values to .dev.vars; never commit that file. The checked-in example documents every supported value.

ValueUsed for
RESEND_API_KEYInvitation, reminder, and results email
FROM_EMAILVerified sender identity
BASE_URLLinks back to the deployed voting service
ADMIN_API_KEYOptional API-key administration when Cloudflare Access is not used
REMINDER_DEFAULT_HOURS_BEFORE_CLOSEDefault reminder window
REMINDER_MAX_EMAILS_PER_RUNPer-cron reminder safety cap
NOTION_API_KEYOptional election metadata sync
NOTION_DATABASE_ALLOWLISTNotion databases the admin may link

Generate current Worker binding types after changing wrangler.jsonc:

npm run cf-typegen

Tests

npm test
npm run test:unit
npm run test:integration

The full suite uses Vitest and @cloudflare/vitest-pool-workers. See Testing for its runtime, mocks, and known schema gap.

Database migrations

Migrations are ordered SQL files in migrations/.

# Local database
npx wrangler d1 migrations apply ihnyc-rc-vote
 
# Production database
npx wrangler d1 migrations apply ihnyc-rc-vote --remote

Check the production migration list first

Take a current Time Travel bookmark, apply pending migrations in order, and verify the affected tables before deploying code that depends on them. Several migrations rebuild elections while preserving dependent rows.

Inspect the remote schema when a migration or route reports a missing column:

npx wrangler d1 execute ihnyc-rc-vote --remote \
  --command="SELECT name, sql FROM sqlite_master WHERE type='table';"

The migration reference lists files 001 through 016, rebuild behavior, and focused checks.

Deploy

  1. Run npm test.
  2. Confirm required remote migrations are applied.
  3. Set each production secret with npx wrangler secret put NAME.
  4. Deploy with:
npm run deploy

After deployment:

  1. Open the public service and load an election that is safe to inspect.
  2. Open the protected admin surface through its normal access path.
  3. Confirm the scheduled handler and email paths show no new errors in Worker logs.
  4. Run the focused database checks from the migration that shipped.

Do not submit a production ballot as a deployment probe. Use the automated suite and a test election.

Back up and restore

Cloudflare D1 Time Travel is automatic. Its current retention is 30 days on Workers Paid plans and 7 days on Free plans.

# Record the current bookmark
npx wrangler d1 time-travel info ihnyc-rc-vote
 
# Find the bookmark for an RFC 3339 timestamp
npx wrangler d1 time-travel info ihnyc-rc-vote \
  --timestamp="2026-01-03T20:20:00+00:00"

A restore overwrites the remote database

Confirm the database, timestamp, and bookmark before continuing. Keep the previous bookmark printed by Wrangler so the restore can be undone.

npx wrangler d1 time-travel restore ihnyc-rc-vote --bookmark=BOOKMARK

See Cloudflare’s Time Travel reference for current limits and restore behavior.

Failure checks

SymptomCheck first
Ballot request is rejectedElection window, ballot type, token state, and the current constraints in Voting system
Invitation or reminder did not sendWorker logs, Resend configuration, invite status, and Email system
Results appear staleElection visibility, manual-results state, and the cache behavior in Results calculation
Notion metadata does not syncNOTION_API_KEY, the allowlist, page properties, and Notion setup
A route reports a missing table or columnRemote migration list and the migration reference