Bug: runJob computed since = sub.last_digest_at, fetched events with
received_at > since, sent the digest (slow), then deleted ALL events
with received_at > since. Any /notify event that arrived between the
fetch and the delete was also received_at > since, so it was deleted
without ever being sent in a digest.
Two changes:
1. Delete by exact event IDs (src/database.ts, src/worker/email.ts):
Added deleteEventsByIds(subscriptionId, eventIds) which deletes
only the events that were actually fetched + sent. The old
timestamp-based delete is retained but no longer called from runJob.
2. Re-fetch subscription on each cron tick (src/worker/email.ts):
createJob's closure captured the original sub, so sub.last_digest_at
stayed stale in memory. Every subsequent tick recomputed since from
the old value, re-fetching and re-sending duplicate events. Now each
tick re-fetches the subscription from the DB via getSubscriptionById
before calling runJob.
Fixes bead mailship-091
The buildParameters function returns events sorted newest-first
(sortBy created_at, slice(0,100)), and the mjml section is already
labeled 'Latest Activity'. The object keys Popular/HasPopular no
longer reflect the semantics, so rename them to Latest/HasLatest.
Resolve 21 pre-existing @typescript-eslint/no-unused-vars errors across
5 files (actions.ts, alert.ts, digest.ts, env.ts, worker/email.ts) that
were blocking the script/checks gate. All removals are unused imports
and unused variable assignments with no runtime impact.
When a confirmed subscriber calls PUT /subscription/email with a changed
frequency, db.updateSubscription updates the row but the running CronJob
captured the original frequency in createJob and was never rescheduled.
A subscriber switching daily→weekly kept the daily cadence until restart.
Fix: in actions.ts:registerSubscription, call worker.registerSubscription(sub)
when the subscription is already confirmed, so addJob stops the old job and
creates a new one with the updated frequency.
Changes:
- src/actions.ts: add else branch calling worker.registerSubscription when
sub.confirmed_at is set
- test/reschedule-on-frequency-change.test.js: new failing-before/passing-after
test verifying the cron expression is updated after frequency change
Closes mailship-e04
The server was setting Access-Control-Allow-Origin: * on every route,
including the unauthenticated GET /subscription/email which returns the
subscriber's email address. Any website could query known pubkeys and
harvest emails.
Changes:
- src/env.ts: require CORS_ORIGIN env var (fail closed, no wildcard)
- src/server.ts: scope CORS middleware to browser-facing routes only,
skip /notify (server-to-server), add Vary: Origin header, import
CORS_ORIGIN from env instead of defaulting to '*'
- .env.template: document new CORS_ORIGIN variable
- test/cors.test.sh: verify CORS on browser routes, no CORS on
server-to-server routes, Vary: Origin presence
Lines 8 and 22 of digest.mjml hardcoded #7161FF for the event-item
border-left and footer link color, while mailer.ts already passes
brandAccent into the template (used at lines 15 and 34). When
BRAND_ACCENT is customized, the border and footer links stayed the
default purple.
Drive both from {{brandAccent}} so they respect the customization.
Fixes bead mailship-hu5
Remove the browser admin SPA under web/ that let users log in via
a Nostr signer and manage subscription filters. The server is now
a headless API only.
Changes:
- Delete web/ directory entirely (SPA source, config, deps)
- Remove express.static('web/dist') serving from server.ts
- Simplify GET / handler to always return JSON (no fallback)
- Remove build:web from package.json build pipeline
- Remove web build steps from Dockerfile
- Remove web references from build-in-production.sh
Kept: core subscription/unsubscribe/confirm/notify backend and
transactional src/pages/*.html (part of email flow, not the UI).
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.
- test/integration.sh: runs full E2E flow against fresh server
- pnpm test and pnpm test:server scripts added
- Root endpoint handles missing web UI gracefully (returns JSON)
- database.ts reads DATA_DIR env var for test isolation
- .gitignore updated for test artifacts