Documentation v1

From zero to first delivered email in minutes.

Complete reference for deploying EmailFlare, authenticating your API key, using /v1/send with all its fields including themeId, and resolving common errors.

🚀

Quickstart

#quickstart
1
Configure your environment

Copy the example env file and set the required variables. CF_API_TOKEN and CF_ACCOUNT_ID come from Cloudflare. Generate random secrets for ADMIN_TOKEN and SESSION_SECRET.

cp .env.example .env.local
# Minimum required
ADMIN_TOKEN=$(openssl rand -hex 32)
SESSION_SECRET=$(openssl rand -hex 32)
CF_API_TOKEN=your_cloudflare_api_token
CF_ACCOUNT_ID=your_account_id
2
Start the container

Use the production compose stack. The GHCR image is pulled automatically — no local build needed.

# Production
docker compose --env-file .env.local -f compose.yaml up -d

# Local dev (includes Mailpit — no real emails sent)
docker compose --env-file .env.local -f compose.dev.yaml up
3
Verify and open the dashboard
curl http://localhost:8090/health
# → 200 OK

# Admin dashboard
http://localhost:8090
4
Add a domain, create an API key, send

Add a Cloudflare sending domain in the Domains page, then generate an API key from the Keys page. Use a test key first to send via SMTP without real Cloudflare delivery.

🔑

Authentication

#auth

Every request to /v1/send requires a bearer token. Use an EmailFlare API key, never your Cloudflare root token.

Authorization: Bearer eflive_xxxxxxxxxxxxxxxxxx
Content-Type: application/json

Key types

⚗ test key

Prefix: eftest_. Sends go through SMTP — no Cloudflare Email Sending credentials required. Safe for local and staging.

✓ live key

Prefix: eflive_. Sends go through Cloudflare Email Sending. Requires valid CF_API_TOKEN and verified domain.

💡 Domain-scoped keys can only send from domains listed in that key's permissions. Requests with an unauthorized domain return 403.
📤

POST /v1/send

#api-send

The single endpoint for all transactional sends. Use templateSlug / templateId for stored templates, or pass raw html / text directly.

Request fields

Field Type Required Description
from string required Sender email address. Must match a verified domain authorized for your key.
fromName string optional Display name shown in the From header, e.g. Acme Inc.
to string or string[] required Recipient address or array of up to 50 addresses. Duplicates are deduplicated.
subject string optional Overrides the template subject. Required for raw html/text sends.
templateSlug string conditional Stable slug-based template reference. Preferred for application code.
templateId string conditional Internal ID-based template reference. Use templateSlug when possible.
themeId string optional Applies a built-in colour theme to layout-based templates. See theme list below. Defaults to default.
variables object optional Key/value map for {{variable}} interpolation in subject and body.
html string conditional Raw HTML body. Used when not referencing a template.
text string conditional Plain-text body. Can be combined with html.
replyTo string optional Reply-to address for the recipient.
At least one of templateSlug, templateId, html, or text must be provided.

cURL

curl -X POST https://your-emailflare.com/v1/send \
  -H "Authorization: Bearer eflive_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "hello@yourdomain.com",
    "fromName": "Acme",
    "to": "alex@example.com",
    "templateSlug": "welcome",
    "themeId": "ocean",
    "variables": { "name": "Alex", "appName": "Acme" }
  }'

fetch (JavaScript)

const res = await fetch('https://your-emailflare.com/v1/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer eflive_xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: 'hello@yourdomain.com',
    to: 'alex@example.com',
    templateSlug: 'welcome',
    themeId: 'ocean',
    variables: { name: 'Alex', appName: 'Acme' },
  }),
});
const data = await res.json();

Response (200)

{
  "results": [{
    "to": "alex@example.com",
    "cfId": "cf_msg_xxxxxxxxxxxxxxxxxx"
  }]
}
🎨

Templates & themes

#templates

EmailFlare ships 50 layout-based templates built with React Email. Reference them by their templateSlug. Pass themeId to switch colour palettes per send — no CSS edits needed.

Available themes

default
ocean
forest
violet
slate
💡 Use the admin Playground to preview any template with any theme before sending to real recipients.

Template reference tips

  • Prefer templateSlug over templateId — slugs are stable across restores.
  • Variable names follow the {{camelCase}} convention used in the template body and subject.
  • If a variable is missing from the payload, it renders as the literal {{variableName}} placeholder.
  • themeId only applies to layout-based templates. Custom HTML templates ignore it.
⚠️

Error responses

#errors
Status Meaning Common cause
401 Unauthorized Missing or invalid Authorization header.
403 Forbidden Key scope does not authorize the sender domain.
404 Not found templateSlug or templateId does not exist.
422 Validation error Missing required fields or invalid email format.
429 Rate limited Per-key send limit reached. Check X-RateLimit-Reset header.
502 All recipients failed Downstream delivery failed for every address in the request.
500 Server error Unexpected backend error — check container logs.

Rate limiting headers returned on every /v1/send response:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1746403200
🔧

Troubleshooting

#troubleshooting

401 on /v1/send

Confirm the header is exactly Authorization: Bearer <key> with no extra spaces. Verify the key is marked active on the Keys page.

403 with a domain-scoped key

The from address domain must be included in the key's allowed domains. Create a global scoped key to bypass domain restrictions while debugging.

Template not found (404)

Copy the slug exactly from the Templates page — it is case-sensitive. System templates use slugs like welcome, magic-link, otp.

Theme not applied to email

themeId only works with layout-based (built-in) templates. For custom HTML templates the field is ignored — you control the styles directly in the HTML body.

Sends succeed in test mode but fail in live mode

Test keys use SMTP and don't require Cloudflare credentials. Live keys use Cloudflare Email Sending — ensure CF_API_TOKEN has the correct permissions and the sender domain is verified. See the Cloudflare token guide →

Database errors in logs

If you see "This SQL statement is not allowed on /query endpoint", upgrade to the latest EmailFlare image — this was a bug fixed in the backend send route.

📋 Always check container logs first: docker compose logs -f emailflare. Most issues are visible in the startup or request output.