API guide

Everything is JSON over HTTPS. The full contract lives in the OpenAPI specification — import it into Postman/Insomnia or generate a typed client.

Authentication

SurfaceBase pathHeader
Messaging (send, read, templates, stats)/api/v2/serverX-Server-API-Key
Management (orgs, servers, domains, …)/api/v2/adminX-Admin-API-Key or Authorization: Bearer
Accounts (login, 2FA, SSO)/api/v2/authAuthorization: Bearer

For application integration you only need one credential: create an API credential for your mail server in the dashboard (Server → Credentials) and put it in X-Server-API-Key.

The response envelope

{ "status": "success", "time": 0.004, "data": { … } }
{ "status": "error",   "time": 0.004, "error": { "code": "ValidationError", "message": "…" } }

Branch on error.code, not on prose. The codes are stable: Unauthorized, Forbidden, NotFound, ValidationError, ParameterMissing — plus the auth-specific ones documented in the spec.

Send a message

curl -X POST https://mail.yourdomain.com/api/v2/server/messages \
  -H "X-Server-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{
    "from": { "email": "billing@yourdomain.com", "name": "Acme Billing" },
    "to": ["customer@example.com"],
    "subject": "Your receipt",
    "text_body": "Thanks! You paid €49.00.",
    "html_body": "<p>Thanks! You paid <strong>€49.00</strong>.</p>",
    "tag": "receipt",
    "metadata": { "order_id": 1042 }
  }'

The from domain must be a verified sending domain of the server. One message is queued per recipient; the response carries a token per recipient for later lookups. Attachments go in attachments: [{name, content_type, data_base64}].

Node.js

const res = await fetch("https://mail.yourdomain.com/api/v2/server/messages", {
  method: "POST",
  headers: { "X-Server-API-Key": process.env.CAMELMAILER_KEY,
             "Content-Type": "application/json" },
  body: JSON.stringify({
    from: "billing@yourdomain.com",
    to: [user.email],
    subject: "Your receipt",
    text_body: `Thanks! You paid €${amount}.`,
  }),
})
const { status, data, error } = await res.json()
if (status !== "success") throw new Error(`${error.code}: ${error.message}`)

Python

import requests

r = requests.post(
    "https://mail.yourdomain.com/api/v2/server/messages",
    headers={"X-Server-API-Key": KEY},
    json={"from": "billing@yourdomain.com", "to": [user.email],
          "subject": "Your receipt", "text_body": f"Thanks! You paid €{amount}."},
)
body = r.json()
assert body["status"] == "success", body["error"]

Send with a stored template

curl -X POST https://mail.yourdomain.com/api/v2/server/messages/with_template \
  -H "X-Server-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{
    "from": "hello@yourdomain.com",
    "to": ["ada@example.com"],
    "template": "welcome",
    "template_model": { "name": "Ada", "product": "Acme", "action_url": "https://app.acme.com/start" }
  }'

Templates use a safe Mustache subset — {{ variable }}, sections {{#items}}…{{/items}}, inverted sections and dotted paths; output is HTML-escaped by default ({{{ raw }}} opts out). Start from the template library, preview with POST /templates/{permalink}/render.

Query messages & deliveries

# search what you sent
GET /api/v2/server/messages?scope=outgoing&query=receipt&per_page=50
# one message + its delivery attempts
GET /api/v2/server/messages/1042
GET /api/v2/server/messages/1042/deliveries
# counters for dashboards
GET /api/v2/server/stats

Webhooks

Create a webhook on the server (dashboard or admin API) and receive POSTs for message events — deliveries, failures, bounces, opens, clicks. Payloads can be RSA-signed with the installation key so you can verify origin; failed deliveries are retried with backoff. Details: docs/ in the repository.

SMTP, if you prefer

Everything the HTTP API accepts you can also hand over via SMTP (port 25/587 of your instance) using an SMTP credential — useful for frameworks that already speak SMTP. Same pipeline, same tracking.

Rate & size limits

  • Self-hosted: none imposed by the software beyond smtp_server.max_message_size.
  • Cloud: message size 14 MB; API bursts are throttled per key — batch endpoints are the intended path for spikes.