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).
|
||
|---|---|---|
| .agents/skills/beads | ||
| .beads | ||
| .claude | ||
| .codex | ||
| .fragua | ||
| script | ||
| src | ||
| test | ||
| .dockerignore | ||
| .env.template | ||
| .gitattributes | ||
| .gitignore | ||
| .nvmrc | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| build-in-production.sh | ||
| CLAUDE.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| LICENSE | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| remove-pnpm-overrides.js | ||
| tsconfig.json | ||
Mailship
A Nostr email notification server. Receives events pushed from relays via NIP-9a, stores them, and sends daily or weekly digest emails.
Architecture
Flotilla ──HTTP──▶ Mailship (PUT /subscription/email)
│ NIP-98 auth
│ returns {key, callback}
│
└──kind 30390──▶ relay or NPB
│
event matches filter
│
▼
POST /notify/:subId ──▶ Mailship
{id, relay} │
UUID in path ├── fetch event from relay
is the auth ├── store in SQLite (dedup)
│
cron fires ──▶ render digest ──▶ Postmark ──▶ email
Configuration
| Variable | Required | Description |
|---|---|---|
MAILSHIP_SECRET |
✓ | A nostr private key hex string for the server's identity |
MAILSHIP_NAME |
✓ | Name of this Mailship instance |
MAILSHIP_URL |
✓ | Public URL of this instance |
BASE_URL |
✓ | Base URL for callback URLs (same as MAILSHIP_URL typically) |
EVENT_VIEWER_URL |
Base URL of the app event links open in (defaults to Flotilla at https://app.flotilla.social, or anything handling the same /spaces/<relay>/<hash> and /<nevent> URL shapes) |
|
BRAND_NAME |
Name used in email branding (default: Flotilla) |
|
BRAND_ACCENT |
Accent color string used in email branding (default: #7161FF) |
|
BRAND_LOGO |
URL of the logo image shown in the email header. Defaults to <EVENT_VIEWER_URL>/logo.png; if empty/unset, a colored brand name is shown instead |
|
POSTMARK_API_KEY |
✓ | Postmark API key for sending emails |
POSTMARK_SENDER_ADDRESS |
✓ | Verified sender email in Postmark |
DEFAULT_RELAYS |
✓ | Comma-separated list of default relays |
INDEXER_RELAYS |
✓ | Comma-separated list of indexer relays |
SEARCH_RELAYS |
✓ | Comma-separated list of search relays |
PORT |
Port to run on (default: 3000) |
API
PUT /subscription/email
Idempotently register or update an email subscription. Re-sends the confirmation email only when the subscription is new or the email address changed; a frequency change keeps the existing confirmation.
Body: { email, frequency, pubkey }
Auth: NIP-98 (planned)
Response: { key, callback }
GET /subscription/email?pubkey=...
Look up an existing subscription, so clients can avoid re-registering (and re-confirming) when settings haven't changed. Returns 404 if none exists.
Response: { key, callback, email, frequency, confirmed }
DELETE /subscription/:key
Unsubscribe.
Auth: NIP-98 (planned)
Response: { ok: true }
POST /notify/:id
NIP-9a relay push callback. Called by relays or NPB when matching events are found.
Body: { id, relay }
Response: { ok: true, stored: boolean }
Returns 404 if subscription not found or inactive.
GET /confirm?token=...
Confirm email address via link from confirmation email.
GET /unsubscribe?token=...
Unsubscribe via link from digest email.
Development
pnpm install
pnpm run build
pnpm run start
Previewing the digest email
To iterate on the email template, render it with sample data and open the result in your browser:
pnpm run preview:digest
open digest-preview.html
Docker
Quick start
cp .env.template .env
# Fill in your secrets in .env
docker compose up -d
Build and run manually
docker build -t mailship .
docker run -d \
-p 4738:4738 \
-v mailship-data:/data \
--env-file .env \
mailship
Tests
pnpm test # Run integration tests
pnpm test:server # Start server for manual testing
Forked from Anchor
Mailship is a fork of Anchor, stripped of push notification support and adapted for NIP-9a relay push event intake.