Email API reference
Base URL: https://api.signally.cloud. All requests and responses are JSON. Create API keys under API & SMTP in your dashboard.
Authentication
Authorization: Bearer avt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys can send from any verified domain of your account. Keep them secret; revoke compromised keys immediately.
POST /v1/email
Send one message to up to 50 recipients (to + cc + bcc). Costs one credit per recipient.
{
"from": { "address": "[email protected]", "name": "Your Shop" }, // or "[email protected]"
"to": [{ "address": "[email protected]", "name": "Jane" }], // or ["[email protected]"]
"cc": [], "bcc": [],
"reply_to": ["[email protected]"],
"subject": "Your order has shipped",
"html": "<p>Hi Jane…</p>",
"text": "Hi Jane…", // html and/or text
"attachments": [{ "filename": "invoice.pdf", "content": "<base64>", "content_type": "application/pdf" }],
"campaign": "order-updates", // groups the message in analytics
"tags": { "order_id": "1042" }, // up to 8, letters/numbers/_/-
"headers": { "X-Order": "1042" }
}
Response 202:
{ "message_id": "0100018f…", "accepted": 1, "suppressed": [] }
Recipients on your suppression list (hard bounces, spam reports, unsubscribes) are skipped, not charged, and listed in suppressed.
POST /v1/email/batch
One template, up to 500 recipients, each sent individually and personalised with {{merge}} tags (HTML-escaped automatically).
{
"from": "[email protected]",
"subject": "Hi {{name}}, your code is {{code}}",
"html": "<p>Your code is <b>{{code}}</b></p>",
"campaign": "otp",
"recipients": [
{ "address": "[email protected]", "name": "Jane", "merge_data": { "code": "4821" } },
{ "address": "[email protected]", "name": "Otieno", "merge_data": { "code": "9930" } }
]
}
Response 207 with a result per recipient: { "results": [{ "address", "message_id" } | { "address", "error": { "code", "message" } }], "sent": 2 }
GET /v1/messages/{message_id}
Message details and its events (Send, Delivery, Open, Click, Bounce, Complaint, …).
GET /v1/account
Your credit balance, daily limit and account status.
Errors
{ "error": { "code": "insufficient_credits", "message": "Not enough credits…" } }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_from, invalid_recipient, missing_subject, missing_body, too_many_recipients, invalid_tag, invalid_header | Fix the request |
| 401 | unauthorized | Missing or revoked API key |
| 402 | insufficient_credits | Top up under Billing |
| 403 | sender_not_verified, account_not_active | Verify the domain / wait for approval |
| 413 | attachments_too_large | Attachments over 7 MB in total |
| 429 | rate_limited, daily_limit_reached | Retry with backoff / limit resets 00:00 UTC |
| 5xx | upstream_error | Retry with backoff |
SMTP
Host smtp.signally.cloud, port 465 (SSL/TLS), username and password from an SMTP login in your dashboard. The From address must be on a verified domain.
Webhooks
Events: sent, delivered, bounced, complained, opened, clicked, rejected, delayed, failed, unsubscribed. Each POST carries X-Webhook-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + rawBody).
{ "event": "bounced", "message_id": "0100018f…", "recipient": "[email protected]", "timestamp": "2026-10-18T09:12:44.000Z",
"campaign": "order-updates", "campaign_id": null, "bounce": { "type": "Permanent", "subtype": "General", "diagnostic": "smtp; 550 5.1.1 user unknown" },
"webhook_id": 3 }