Skip to content

Minis233/postq

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

postq

A self-hosted email sending service on Cloudflare Workers, built on the native send_email binding (Cloudflare Email Service Workers API). No third-party email provider, no SMTP credentials — Cloudflare sends the mail directly from your Worker.

Features

  • Web UI — compose emails (to/cc/bcc, HTML + plain text, file attachments), manage templates, and browse send history. Dark/light auto theme.
  • REST APIPOST /api/send with a single Bearer token.
  • Templates — stored in KV with {{variable}} substitution.
  • Send history — bounded log in KV (newest first), bcc kept as count only.
  • Rate limiting — per-token fixed window (default 60/hour).
  • Full validation — recipient/attachment/size limits checked before send, Cloudflare E_* error codes surfaced back to the caller.

Architecture

Single Worker, no build step:

File Role
src/index.js Router, auth, error mapping, send handler
src/validation.js Payload validation + Cloudflare limits
src/templates.js KV templates + {{var}} rendering
src/history.js KV send log
src/ratelimit.js Per-token fixed-window limiter
src/html.js / src/app.js Single-page UI

Prerequisite: onboard your sending domain

The send_email binding can only send from a domain onboarded to Email Service. This is a one-time dashboard step:

  1. Cloudflare dashboard → Compute → Email Service → Email Sending
  2. Onboard Domain → pick your domain → Done

Cloudflare adds the required DNS records (MX/SPF/DKIM on cf-bounce.<domain>, DMARC on _dmarc.<domain>). Propagation is usually 5-15 min on Cloudflare DNS.

Until this is done, /api/send returns E_INTERNAL_SERVER_ERROR / E_SENDER_DOMAIN_NOT_AVAILABLE.

Setup

npm install
wrangler kv namespace create POSTQ_KV   # paste id into wrangler.toml
wrangler secret put POSTQ_API_TOKEN      # your API / UI token
wrangler deploy

Adjust SEND_DOMAIN, DEFAULT_FROM_LOCAL, and the rate-limit vars in wrangler.toml.

API

All /api/* routes require Authorization: Bearer <POSTQ_API_TOKEN>.

Send

curl -X POST https://postq.YOUR-SUBDOMAIN.workers.dev/api/send \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "noreply@yourdomain.com",
    "to": ["alice@example.com"],
    "cc": ["bob@example.com"],
    "subject": "Hello",
    "html": "<h1>Hi</h1>",
    "text": "Hi"
  }'

Using a template:

curl -X POST .../api/send -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"to":"a@example.com","template":"welcome","vars":{"name":"Ada"}}'

from defaults to DEFAULT_FROM_LOCAL@SEND_DOMAIN if omitted. Explicit fields override template output. Attachments use base64 content + filename + type.

Other endpoints

Method Path Purpose
GET /api/info domain + limits
GET /api/templates list templates
PUT /api/templates create/update {name,subject,html,text}
DELETE /api/templates/:name delete template
GET /api/history recent sends
DELETE /api/history clear history
GET /healthz public health check

Error codes

Validation and Cloudflare send errors return {success:false, code, error} with an appropriate HTTP status (400 validation, 401 auth, 429 rate/daily limit, 422 suppressed, 500 internal). See Cloudflare's error code reference.

Limits (Cloudflare Email Service)

  • Max 50 combined recipients (to + cc + bcc)
  • Max 32 attachments
  • Max 5 MiB total message size

Local development

node dev-server.mjs   # http://localhost:8791  (token: devtoken)

Mocks the EMAIL binding (logs instead of sending) and uses in-memory KV.

Tests

npm test   # 27 tests: validation, templates, routing, rate limit

License

MIT

About

Self-hosted email sending service on Cloudflare Workers, built on the native send_email binding (Email Service Workers API).

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors