Documentation

Everything the API can do.

One REST API for transactional sending, campaigns, and automation — plus an SMTP front door for anything that already speaks mail. The base URL is https://api.volanea.com and the machine-readable spec lives at GET /v1/openapi.json.

Start here

Quickstart

Grab your secret key from project settings, verify a sending domain, and you can send with one request. The Idempotency-Key header makes retries safe — a repeated key replays the stored response instead of sending again.

curl

curl https://api.volanea.com/v1/send \
  -H "Authorization: Bearer sk_..." \
  -H "Idempotency-Key: order-4921" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "ada@example.com",
    "subject": "Your order shipped",
    "html": "<p>On its way, {{firstName}}.</p>",
    "variables": { "firstName": "Ada" }
  }'

# → { "messages": [{ "id": "…", "to": "ada@example.com", "status": "queued" }],
#     "testMode": false }
# The response returns as soon as the message is durably queued; track final
# delivery with GET /v1/emails/{id} or the email.sent / email.delivered webhooks.

TypeScript — fetch

const res = await fetch("https://api.volanea.com/v1/send", {
  method: "POST",
  headers: {
    Authorization: `Bearer undefined`,
    "Content-Type": "application/json",
    "Idempotency-Key": "order-4921",
  },
  body: JSON.stringify({
    to: "ada@example.com",
    subject: "Your order shipped",
    html: "<p>On its way, {{firstName}}.</p>",
    variables: { firstName: "Ada" },
  }),
});

const { data } = await res.json();

Keys

Authentication

Every request carries an API key as Authorization: Bearer <key>. Each project has three:

sk_…

Secret key

Full access to every endpoint. Server-side only — never ship it to a browser or mobile app.

sk_test_…

Test key

Same access as the secret key, but nothing is delivered: messages render, log, and return results, and domain verification is not enforced. Responses include testMode: true.

pk_…

Public key

Accepted only by POST /v1/events. Safe to embed in client-side code for product event tracking.

Conventions

Errors & pagination

Every error is the same envelope with an appropriate HTTP status — a machine-readable code and a human-readable message. Validation failures (422, code validation_error) add a details array of issues.

error envelope

{
  "error": {
    "code": "domain_not_verified",
    "message": "The domain \"example.com\" is not verified for this project."
  }
}

List endpoints return { data, nextCursor }. Pass nextCursor back as the cursor query parameter until it is null.

Port 2525

SMTP relay

Anything that already speaks SMTP can send through Volanea — the relay parses the message and hands it to the same pipeline as POST /v1/send. Authenticate with any username and your secret key as the password. Unauthenticated sessions are treated as inbound mail: messages addressed to one of your verified domains are stored, the sender becomes a contact, and an email.received event fires (which can trigger webhooks and workflows).

Coming soon — the public SMTP endpoint isn't live yet. Use the REST API today; everything below will work unchanged the moment it opens.

smtp · port 2525 · coming soon

# Any SMTP client works — username is ignored, the password is your secret key.
swaks --server smtp.volanea.com:2525 \
      --auth-user volanea --auth-password sk_... \
      --from hello@yourdomain.com --to someone@example.com \
      --header "Subject: Hello from SMTP"

# nodemailer
const transport = nodemailer.createTransport({
  host: "smtp.volanea.com", port: 2525,
  auth: { user: "volanea", pass: process.env.VOLANEA_SECRET_KEY },
});

Signed deliveries

Webhook verification

Register an endpoint and Volanea POSTs each matching event as JSON, retrying with backoff for about 24 hours. Every delivery is signed with your endpoint's whsec_… secret in the X-Volanea-Signature header — verify it against the raw request body before trusting the payload.

verify X-Volanea-Signature

import { createHmac, timingSafeEqual } from "node:crypto";

// X-Volanea-Signature: t=<unix-ms>,v1=<hex hmac-sha256 of "<t>.<raw body>">
export function verifyWebhook(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
  const timestamp = Number(parts.t);
  if (!parts.v1 || !Number.isFinite(timestamp)) return false;
  if (Math.abs(Date.now() - timestamp) > 5 * 60_000) return false; // reject replays

  const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  return expected.length === parts.v1.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Model Context Protocol

AI agents (MCP)

Volanea ships an MCP server, so an AI coding agent already working in your repo can do the whole integration: build the templates, segments, and workflows in your project, write the sending and webhook code into your codebase, then send test mail and read it back to prove the thing actually works.

There is no separate MCP token — authentication is a project API key as a bearer token, the same key the REST API takes. Use the sk_test_… key; the connect command is pre-filled with it in your project settings.

connect

# Claude Code — run this inside your own project
claude mcp add volanea --transport http https://api.volanea.com/mcp \
  -H "Authorization: Bearer sk_test_..."

# Then just ask for what you want:
#   "Set up Volanea in this app — a welcome email when someone signs up,
#    and a webhook handler that unsubscribes hard bounces."

# Any MCP client works. Cursor, Windsurf, and friends take the same
# two values in mcpServers config: the URL and the Authorization header.

Orient

get_project_status · get_api_reference · list_resources

Reads the plan, reputation, verified domains, and what already exists — so generated code matches the real API instead of a guess.

Build

create_template · create_segment · create_workflow · update_workflow · create_campaign_draft

Creates account state. Idempotent by name, so a retry doesn't leave duplicates behind.

Deliver

setup_domain · create_webhook_endpoint

Registers a sending domain and returns its DNS records; registers an endpoint and returns its whsec_ signing secret.

Verify

send_test_email · check_delivery · track_event · test_workflow

Proves the wiring end to end — fire the event your app would fire, then read the rendered message back.

The surface is deliberately narrower than the REST API. Test sends force test mode no matter which key authenticated, so an agent can never mail a real person. Campaigns are created as drafts for a human to launch. Workflows start paused. Nothing deletes. The code the agent writes into your app is what sends for real, once you deploy it.

Reference

Every endpoint

All 92 of them, each with its own page: parameters, request and response fields, error codes, and a working example in six languages. Generated from the OpenAPI document at /v1/openapi.json, so it never describes an API we do not serve.

Open the API reference

Send

2

Transactional sending, with batching, scheduling, and idempotency.

POST sendPOST send/batch

Events

6

Product event ingestion (public key) and the event log.

POST eventsGET eventsGET events/namesGET events/stats+2

Contacts

13

The contact graph: CRUD, timeline, bulk operations, CSV import/export.

GET contactsPOST contactsGET contacts/exportPOST contacts/import+9

Emails

3

The send log — every message across all sources.

GET emailsDELETE emails/{id}GET emails/{id}

Templates

9

Reusable content with versioning, rollback, and test sends.

GET templatesPOST templatesGET templates/{id}PATCH templates/{id}+5

Domains

5

Sending domains, DNS records, and verification.

GET domainsPOST domainsGET domains/{id}DELETE domains/{id}+1

Segments

10

Dynamic (condition-driven) and static audiences.

GET segmentsPOST segmentsPOST segments/previewGET segments/{id}+6

Campaigns

13

One-off broadcasts with scheduling, A/B subjects, and engagement stats.

GET campaignsPOST campaignsPOST campaigns/audience-countGET campaigns/{id}+9

Workflows

11

Event-triggered automation graphs and their executions.

GET workflowsPOST workflowsGET workflows/{id}PATCH workflows/{id}+7

Webhooks

6

Signed event deliveries to your endpoints, with retries and replay.

GET webhooksPOST webhooksPATCH webhooks/{id}DELETE webhooks/{id}+2

Suppressions

5

The do-not-send list: bounces, complaints, unsubscribes, and manual blocks.

GET suppressionsPOST suppressionsGET suppressions/checkGET suppressions/{id}+1

Stats

2

Engagement aggregates: the project rollup and the per-workflow funnel.

GET statsGET workflows/{id}/stats

Verify

1

Email address validation.

POST verify

Billing

2

Usage, monthly caps, reputation, and invoices.

GET billingPATCH billing

Inbound

2

Inbound mail ingestion (used by the SMTP relay).

POST inboundGET inbound

Meta

2

API metadata.

GET openapi.jsonGET docs