Workspaces & roles
A workspace (organization) owns domains, API keys, messages, and credit accounts. Membership roles gate mutations: Owners manage billing and deletion, Admins operate infrastructure, Members send, Viewers read.
Documentation
These guides mirror the product invariants. The email HTTP contract is the OpenAPI document.
Organizations, membership, and Owner / Admin / Member / Viewer permissions.
Add a domain, publish SPF/DKIM/DMARC, and move through verification states.
Create a Bearer key, send with POST /api/emails, and revoke keys you no longer use.
HTTP enqueues the send. Workers deliver it and post signed events to your endpoints.
OpenAPI 3 for POST /api/emails, list, and get. Bearer API key, idempotency, and the status values the API returns.
Included credits, packs, ledger semantics, and subscription intervals.
Health endpoints, queue workers, and what the admin command center monitors.
A workspace (organization) owns domains, API keys, messages, and credit accounts. Membership roles gate mutations: Owners manage billing and deletion, Admins operate infrastructure, Members send, Viewers read.
Adding a domain creates pending DNS instructions. Background verification jobs check SPF, DKIM, and DMARC. Only verified domains become eligible for active sending.
# Example SPF (illustrative) v=spf1 include:amazonses.com ~all
Create a key in the dashboard and send with Authorization: Bearer. The secret is shown once. Revoked keys stop authenticating immediately.
POST /api/emails checks the Bearer key (scope emails:send) or the dashboard session, the workspace send block, and the verified from-domain, then enqueues a job. The worker checks suppression and credits before it calls the provider. Read status with GET /api/emails/{id}. Customer webhook names are email.sent, email.delivered, email.bounced, email.complained, and email.failed. A complaint is status complained and notifies email.complained. A provider reject is status rejected. A worker failure stays failed. Reject and worker failure both notify email.failed. Provider accept (sent) is not inbox delivery. The contract is openapi/sendaro-v1.yaml. html or text is required. reply_to and scheduled_at are optional. The placeholder key below is not a credential.
curl -X POST https://usesendaro.com/api/emails \
-H "Authorization: Bearer rf_example_key_not_real" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: first-send-1" \
-d '{"from":"[email protected]","to":"[email protected]","subject":"Hello from Sendaro","html":"<p>It works.</p>"}'const response = await fetch("https://usesendaro.com/api/emails", {
method: "POST",
headers: {
Authorization: "Bearer rf_example_key_not_real",
"Content-Type": "application/json",
"Idempotency-Key": "first-send-1",
},
body: JSON.stringify({
"from": "[email protected]",
"to": "[email protected]",
"subject": "Hello from Sendaro",
"html": "<p>It works.</p>"
}),
});
const email = (await response.json()) as { id: string; object: "email" };A TypeScript client for these three routes is in this repository at src/sdk. It is not published to npm. From the repository root, import ./src/sdk. Inside this app the same module is @/sdk. The default base URL is https://sendaro.email. send posts from, to, subject, and optional html, text, reply_to, and scheduled_at, with an optional Idempotency-Key. At least one of html or text is required. list takes an optional limit. get reads one id. A non-2xx response throws SendaroError with the API error string and the HTTP status. There is no batch, attachment, or separate schedule method. The placeholder key is not a credential.
import { Sendaro } from "./src/sdk";
const client = new Sendaro({ apiKey: "rf_example_key_not_real" });
const accepted = await client.send(
{
from: "[email protected]",
to: "[email protected]",
subject: "Hello from Sendaro",
html: "<p>It works.</p>",
},
{ idempotencyKey: "first-send-1" },
);
const message = await client.get(accepted.id);
const page = await client.list({ limit: 50 });Plans and credit packs live in Postgres. Subscriptions reference plan rows; credit accounts update through an immutable ledger. Marketing pricing reads the same tables your billing UI will.
GET /api/health reports database and queue readiness. Workers consume BullMQ queues for domain verification, sends, and webhook delivery. The admin command center observes jobs without bypassing tenant authz for customer data mutations.