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
| Surface | Base path | Header |
|---|---|---|
| Messaging (send, read, templates, stats) | /api/v2/server | X-Server-API-Key |
| Management (orgs, servers, domains, …) | /api/v2/admin | X-Admin-API-Key or Authorization: Bearer |
| Accounts (login, 2FA, SSO) | /api/v2/auth | Authorization: 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.