Vela API reference
One key sends mail. Inbound needs no key at all. It arrives over SMTP and Vela writes it into whichever destination you configured. Create a key under API keys in the dashboard; the secret is shown once.
Keys
Send the key as a bearer token. A key carries a permission: full sends and reads, sending only sends, and read only reads. It also carries an expiry you choose when you create it, or no expiry at all. Revoking a key refuses every request made with it from that moment.
curl https://vela.novacraft.africa/api/v1/emails \ -H "Authorization: Bearer vela_live_…"
Sending
/api/v1/emailssendingSend one message as a verified domain. Resend-shaped, so a client written against that API needs only a new base URL.
curl -X POST https://vela.novacraft.africa/api/v1/emails \
-H "Authorization: Bearer vela_live_…" \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Your invoice is ready",
"html": "<p>Thanks, it is attached below.</p>",
"attachments": [
{ "filename": "invoice.pdf", "content": "JVBERi0xLjcK…" }
]
}'
# → { "id": "b7c3…" }Each attachment carries its bytes as base64 content; Vela does not fetch a path. Give one a content_id and it travels inline, drawn wherever the html says <img src="cid:…">. Up to 28 MB in total, and a scheduled send cannot carry files yet.
Errors
| Status | Name | Means |
|---|---|---|
| 401 | missing_api_key | No bearer token on the request. |
| 401 | invalid_api_key | The key is unknown, revoked, or past its expiry. |
| 403 | restricted_api_key | The key may only read mail, and the call sends or changes something. |
| 422 | validation_error | The body is missing a field, or an address is malformed. |
| 422 | all_recipients_suppressed | Every recipient is suppressed, so nothing was sent. When only some are, the rest are sent and the reply lists the skipped ones under suppressed. |
| 502 | delivery_failed | The provider refused the message. The attempt is still recorded against the key. |
Suppressions
An address that bounces for good or reports spam is suppressed, and Vela will not send to it again. Bounces and complaints also land on a list every workspace shares, because one sender reputation carries all of them. Check recipients before you send to show the reason in your own interface instead of finding a skipped address afterwards.
/api/v1/suppressions/checkany keyWhich of up to 200 addresses this workspace will not send to, why, and whether the shared list holds them. Only the addresses asked about are answered.
/api/v1/suppressionsany keyThe workspace list, newest first. Pass ?limit= for up to 1000.
/api/v1/suppressionssending or fullAdd an address with reason bounce, complaint or manual. It joins this workspace only; the shared list fills from bounces and complaints Vela itself receives.
/api/v1/suppressions?address=sending or fullLift the workspace entry. The reply carries still_blocked when the shared list holds the address too; only Novacraft can lift that one.
curl -X POST https://vela.novacraft.africa/api/v1/suppressions/check \
-H "Authorization: Bearer vela_live_…" \
-H "Content-Type: application/json" \
-d '{ "addresses": ["[email protected]", "[email protected]"] }'
# → { "object": "list", "data": [
# { "address": "[email protected]", "reason": "bounce",
# "detail": "smtp; 550 5.1.1 user unknown",
# "createdAt": "2026-10-08T08:33:14.545Z", "shared": true } ] }Inbound
Point the domain's MX at Vela and every message is accepted, checked against SPF, DKIM and DMARC, parsed, and written to each destination that matches. Four kinds: webhook, postgres, libsql and s3. A delivery that fails is retried with backoff and every attempt keeps the status it got back.
Webhook payload
POST https://api.yourapp.com/hooks/mail
svix-id: msg_…
svix-timestamp: 1757980800
svix-signature: v1,<base64>
{
"type": "mail.received",
"data": {
"id": "…",
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Invoice #4821",
"spf": "PASS",
"dkim": "PASS",
"dmarc": "PASS"
}
}Verifying the signature
Vela signs with the Svix scheme, so an existing Resend or Svix verifier works unchanged. Concatenate the id, the timestamp and the raw body with dots, HMAC-SHA256 it with the destination secret, and compare in constant time.
const signed = `${svixId}.${svixTimestamp}.${rawBody}`
const expected = createHmac('sha256', secret).update(signed).digest('base64')
// header is "v1,<expected>"Turn on includeRaw for a destination and the original MIME travels with the payload, attachments included.
MCP
The same keys open an MCP server. Give an agent a read key to read the mailbox. Over MCP a key never both reads and sends: a full key is read-only here, and send_email takes a sending key. Mail an agent reads is written by outside senders, and an agent that could also send would let that mail tell it what to send.
{
"mcpServers": {
"vela": {
"url": "https://vela.novacraft.africa/api/mcp",
"headers": { "Authorization": "Bearer vela_live_…" }
}
}
}| Tool | Needs | Does |
|---|---|---|
list_domains | read or full | Domains on the workspace, their direction, and whether DNS is verified. |
list_messages | read or full | Messages newest first, filtered by direction, domain or a search string. |
get_message | read or full | One message with its headers, auth results and every delivery attempt. |
list_destinations | read or full | Destinations and their last result. Credentials are never returned. |
list_deliveries | read or full | Delivery attempts, filtered by pending, delivered or dead. |
retry_delivery | full | Queue a failed delivery for another attempt. |
send_email | sending | Send one message as a verified domain. |
workspace_status | read or full | Counts of domains, destinations, messages and queued deliveries. |
Dashboard routes
These authenticate with your signed-in session rather than an API key, and back the dashboard itself. They are documented so you can script against the same contract from a browser context.
| Route | Methods | Does |
|---|---|---|
/api/v1/vela/domains | GET · POST · DELETE | Sending and receiving domains, with the DNS records each one needs. |
/api/v1/vela/destinations | GET · POST · DELETE | Where inbound mail is written: webhook, postgres, libsql or s3. |
/api/v1/vela/messages | GET | Inbound and sent messages, with their attachments and raw MIME. |
/api/v1/vela/events | GET | Delivery attempts, with the status each destination returned. |
/api/v1/vela/keys | GET · POST · DELETE | API keys. Creating or revoking one asks for your password again. |
/api/v1/vela/send | POST | Send as a signed-in operator, without minting a key first. |