- Replace empty NIP-9a URL (first paragraph and API section header)
with a link to https://github.com/nostr-protocol/nips/blob/master/9a.md
- Add Node >= 22 requirement to Development section
- Add single-instance constraint note about in-process cron jobs
The endpoint accepts an optional field for NIP-98 include_event
support. When provided, the event is verified inline (id match + signature
check). When omitted, the event is fetched from the relay. Documents the
{ ok: true, stored: boolean } response and dedup behavior.
Bug: when the event was not found at the relay, the handler returned
{ ok: true, skipped: true }, which did not match the documented contract
{ ok: true, stored: boolean } in README.md.
Fix: change the 'skipped' response to { ok: true, stored: false }, so the
response shape is consistent across all code paths.
- src/server.ts: changed line 287 from { ok: true, skipped: true } to
{ ok: true, stored: false }, with updated comment
- README.md: added a note documenting the skip case (stored: false)
- test/notify-response-shape.test.ts: new test that asserts stored=false
and that skipped is never present
- Add vitest as dev dep (pnpm add -D vitest), create vitest.config.ts
with globals:true and setupFiles
- Migrate digest-template, normalize-relay-url, reschedule-on-frequency-change,
and event-arrival-race from raw JS + hand-rolled asserts to vitest
describe/it/expect, as .test.ts (port all assertions without weakening)
- Create test/setup.ts with env vars needed by env.ts/mailer.ts
- DELETE test/web-ui.test.js: dead test for removed web UI (PR #1)
- package.json: add test:unit script (vitest run), keep test = bash E2E
- script/checks: add pnpm test:unit after build
- README: document pnpm test:unit / pnpm test
- src/env.ts: remove required-PORT throw, default to '4738' when unset
- README.md: change documented default from 3000 to 4738
All other files (.env.template, Dockerfile, docker-compose.yml)
already use 4738 — no further changes needed.
Three browser-facing endpoints now require a kind-27235 HTTP auth event
(NIP-98) proving the caller controls the pubkey:
- GET /subscription/email — pubkey extracted from auth header instead
of query param; returns subscription for the authed pubkey.
- PUT /subscription/email — pubkey extracted from auth header instead
of trusting a client-supplied body field.
- DELETE /subscription/:key — verifies auth pubkey matches subscription
owner (returns 403 if mismatch).
Server-side: decode base64 'Nostr <b64>' Authorization header, JSON.parse,
check kind === 27235, verifyEvent (nostr-tools/pure), then check u /
method / payload tags against the request URL / method / body.
README updated to reflect 'implemented' auth (not 'planned').
Integration test updated to generate NIP-98 auth headers via a new helper
script (script/nip98-auth-header.mjs).
Replace all Postmark references (POSTMARK_API_KEY, POSTMARK_SENDER_ADDRESS)
with SMTP configuration (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD,
SMTP_FROM) across documentation, deployment config, template, and test.
- README.md: architecture diagram (Postmark → SMTP) and configuration table
- docker-compose.yml: POSTMARK_* env vars replaced with SMTP_* required vars
- .env.template: POSTMARK_* entries replaced with SMTP_* entries
- test/integration.sh: POSTMARK_* test exports replaced with SMTP_* test values
Switch the subscribe endpoint from POST to an idempotent PUT so clients can
always upsert without a stateful lookup first.
- Add GET /subscription/email?pubkey= lookup so clients can avoid re-POSTing.
- insertSubscription no longer clears confirmed_at unless the email address
changes; a frequency change keeps the existing confirmation.
- registerSubscription only sends a confirmation email when the subscription
is new, unconfirmed, or the email address changed.
- Update integration test and README for the PUT endpoint.
Multi-stage Dockerfile:
- Stage 1: install deps, build TS, build web UI
- Stage 2: minimal production image with sqlite, only prod deps
- Exposes port 4738, uses /data volume for SQLite
docker-compose.yml for easy local deployment with .env support.
.dockerignore to keep the build context lean.
README updated with Docker usage.
.env.template adds DATA_DIR.