Skip to content

notify-flow

Self-hosted transactional email for Cloudflare Workers, over plain SMTP

Why notify-flow ​

Every app ends up sending the same emails: welcome, email verification, password reset, login alerts. notify-flow keeps those emails in one place:

  • One integration. Apps call a REST endpoint instead of each embedding its own SMTP logic and credentials.
  • Templates, not raw HTML. Callers pick a template and pass variables. They can't send an arbitrary subject or body, which closes off spam and impersonation.
  • Your own templates. Each client can register its own templates, or override the built-in ones, in it, en and pt-BR.
  • Any SMTP provider. Gmail and Microsoft 365 are configuration presets on top of a generic SMTP client, not hard dependencies.

How it works ​

text
Frontend ──► Your backend ──► notify-flow (Worker) ──► SMTP server ──► Recipient
                 │                  │
                 │                  ├─ authenticate the client (API key)
                 │                  ├─ validate payload and rate limit
                 │                  ├─ pick the template (by name or event), render subject, HTML, text
                 │                  ├─ send over TLS / STARTTLS, now or from a queue, retrying transient errors
                 │                  └─ log the delivery in D1
                 └─ owns auth logic (tokens, reset URLs, user checks)

notify-flow only delivers mail. Security logic such as generating reset tokens stays in your backend.

Send your first email ​

bash
curl -X POST "$NOTIFY_FLOW_URL/v1/email/send" \
  -H "Authorization: Bearer $NOTIFY_FLOW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: welcome:user-123" \
  -d '{ "template": "welcome", "to": "jane.doe@example.com",
        "variables": { "appName": "Acme", "name": "Jane" } }'

The response is {"id":"msg_…","status":"submitted"}: the SMTP server accepted the message.