# 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 ──▶ SMTP ──▶ 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) | | `SMTP_HOST` | ✓ | SMTP server hostname | | `SMTP_PORT` | ✓ | SMTP server port | | `SMTP_USER` | ✓ | SMTP username | | `SMTP_PASSWORD` | ✓ | SMTP password | | `SMTP_FROM` | ✓ | From email address for outgoing mail | | `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//` and `/` 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 `/logo.png`; if empty/unset, a colored brand name is shown instead | | `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 ```sh 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: ```sh pnpm run preview:digest open digest-preview.html ``` ## Docker ### Quick start ```sh cp .env.template .env # Fill in your secrets in .env docker compose up -d ``` ### Build and run manually ```sh docker build -t mailship . docker run -d \ -p 4738:4738 \ -v mailship-data:/data \ --env-file .env \ mailship ``` ## Tests ```sh pnpm test # Run integration tests pnpm test:server # Start server for manual testing ``` ## Forked from Anchor Mailship is a fork of [Anchor](https://github.com/coracle-social/anchor), stripped of push notification support and adapted for NIP-9a relay push event intake.