Commit graph

36 commits

Author SHA1 Message Date
cfbb29cb96 Merge pull request 'Fix digest job race: events received between fetch and delete are dropped unsent' (#8) from mailship-091-event-arrival-race-in-digest-job-events--614 into main
Reviewed-on: #8
Reviewed-by: matt <matt@lorentz.is>
2026-09-14 17:12:10 +00:00
Agent
737f949f9b fix digest job race: delete sent events by ID, not timestamp
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
2026-09-14 13:07:34 -04:00
Agent
e83cd1f7d2 Rename Popular/HasPopular → Latest/HasLatest in digest ts/mjml/render-preview
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.
2026-09-14 13:00:07 -04:00
mplorentz
c72b86e4ae Merge remote-tracking branch 'origin/main' into mailship-0da-readme-config-architecture-and-docker-co-6e1 2026-09-14 12:55:35 -04:00
Agent
2e26760126 fix: remove unused imports and variables flagged by eslint
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.
2026-09-10 17:03:35 -04:00
Agent
0eca031890 fix: update stale 'Postmark' comment to 'SMTP' in server.ts
The subscription error fallback comment still referenced Postmark
after the mailer migration. Update it to reflect the SMTP mailer.
2026-09-10 16:59:15 -04:00
694791bfdd Merge pull request 'Reschedule cron job when confirmed subscriber changes frequency' (#2) from mailship-e04-frequency-change-updates-db-but-never-re-63d into main
Reviewed-on: #2
Reviewed-by: matt <matt@lorentz.is>
2026-09-10 19:55:42 +00:00
8837d3fadd Merge pull request 'Scope CORS to browser routes only, fail closed on CORS_ORIGIN' (#4) from mailship-32x-cors-is-not-production-safe-wildcard-ori-52b into main
Reviewed-on: #4
Reviewed-by: matt <matt@lorentz.is>
2026-09-10 19:46:02 +00:00
Agent
8866ef3256 merge: main into branch to pick up lint fixes 2026-09-10 12:27:45 -04:00
Agent
a4463fdab4 Merge remote-tracking branch 'origin/main' into mailship-32x-cors-is-not-production-safe-wildcard-ori-52b 2026-09-10 12:25:26 -04:00
Agent
def67c4b6c merge origin/main: resolve digest.ts conflict (identical eslint cleanup) 2026-09-10 12:23:37 -04:00
Agent
57add6dcbd Merge remote-tracking branch 'origin/main' into mailship-32x-cors-is-not-production-safe-wildcard-ori-52b 2026-09-10 12:02:17 -04:00
Agent
4497a7dee9 fix: remove pre-eslint unused-variable errors across src/
Remove 21 unused imports/variables that caused eslint failures:
- actions.ts (2): Subscription type import, getCronExpression import
- alert.ts (4): CronExpressionParser, tryCatch, int, HOUR
- digest.ts (12): now, nth, nthEq, dateToSeconds, getIdFilters,
  getReplyFilters, Loader, AdapterContext, makeLoader, SocketAdapter,
  call, loadRelaySelections
- env.ts (1): netContext
- worker/email.ts (1): purgeJob variable (kept CronJob side-effect)

All unused symbols were pre-existing, not related to changed files.
tsc --noEmit passes; script/checks now returns 0 on the src check.
2026-09-10 11:58:05 -04:00
Agent
97e92232c7 chore: remove unused import of netContext from env.ts
eslint flagged netContext as imported but never used (pre-existing).
Removing the unused import so the repo's eslint gate passes on the
modified area.
2026-09-10 11:31:44 -04:00
Agent
3e667fe3c6 fix: reschedule cron job when confirmed subscriber changes frequency
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
2026-09-10 11:30:56 -04:00
Agent
fdc4579aa7 fix: scope CORS to browser routes only, require CORS_ORIGIN (fail closed)
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
2026-09-10 11:29:18 -04:00
Agent
97eaf8ab36 fix(digest): replace hardcoded #7161FF with {{brandAccent}}
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
2026-09-10 11:23:31 -04:00
Agent
2fb738981e Fix pre-existing eslint unused-import errors to pass script/checks gate
Remove 20 no-unused-vars violations across 5 files:
- src/actions.ts: unused Subscription type import, getCronExpression
- src/alert.ts: unused cron-parser and @welshman/lib imports
- src/digest.ts: unused now, nth, nthEq, dateToSeconds, getIdFilters,
  getReplyFilters, Loader, AdapterContext, makeLoader, SocketAdapter,
  call, loadRelaySelections
- src/env.ts: unused netContext import
- src/worker/email.ts: assigned-but-unused purgeJob variable

These were pre-existing errors unrelated to the web UI removal.
2026-09-10 10:36:54 -04:00
Agent
75f5908039 Strip out the web UI (login + subscription filter management)
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).
2026-09-10 10:08:14 -04:00
mplorentz
d5f54845b6 dedupe and serialize subscription writes. 2026-09-03 13:44:02 -04:00
mplorentz
5abec46cb7 Make subscription upsert idempotent via PUT
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.
2026-09-03 11:47:21 -04:00
mplorentz
ff8e1d7a0d Update styling on confirmation failed page 2026-09-02 16:51:01 -04:00
mplorentz
3a0aff2f30 Update confirmation page 2026-08-27 15:23:18 -04:00
mplorentz
cf335649c0 Wait longer for profile photoes. 2026-08-27 15:18:52 -04:00
mplorentz
14bc273d98 update digest footer 2026-08-27 15:01:13 -04:00
mplorentz
777ab81195 Add relay to nevent in digest links 2026-08-27 14:49:56 -04:00
mplorentz
d143a563ba Process nostr events included directly in the callback. 2026-08-27 11:28:03 -04:00
mplorentz
4e8918c1f3 Add branding to email confirmation screen 2026-08-27 10:30:46 -04:00
mplorentz
f7e0c99764 Update subscription confirmation email template 2026-08-27 10:17:38 -04:00
mplorentz
6e35717072 Rework email template 2026-08-26 17:57:26 -04:00
mplorentz
77e35c26c7 log errors 2026-08-26 17:21:00 -04:00
mplorentz
1865128f41 Switch postmark api to smtp 2026-08-24 16:12:32 -04:00
mplorentz
a799e1ba1c add permissive CORS headers 2026-08-20 12:42:31 -04:00
Agent
f220301154 Add integration test script, fix root handler, add DATA_DIR support
- 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
2026-08-18 12:35:20 -04:00
Agent
25645e1d0e Fix unsubscribe page path, remove pool override, handle Postmark error gracefully
- Fix unsubscribe page path (unsubscribe-success.html → unsubscribe.html)
- Remove netContext.pool.get override that crashed load()
- Handle Postmark registration error gracefully by returning subscription from DB
- Add getSubscriptionByPubkey import to server.ts
2026-08-18 12:26:07 -04:00
Agent
21e7058862 Initial mailship fork from anchor
Fork anchor, strip all push notification code (APNs, FCM, WebPush),
rename from anchor to mailship, add new database schema for
subscriptions + events tables, and add HTTP API for email
notification registration and NIP-9a relay push callbacks.

- POST /subscription/email — register for email digests
- DELETE /subscription/:key — unsubscribe
- POST /notify/:id — NIP-9a relay push callback
- GET /confirm?token=... — confirm email
- GET /unsubscribe?token=... — unsubscribe

Co-authored-by: mplorentz
2026-08-18 12:21:22 -04:00