Transactional + broadcast email with provider-isolated reputation, BYOD (bring your own domain), inbound routing, and reply threading.
Features
- **BYOD (bring your own domain)** — one-time DNS setup, real Resend Domains integration (POST /domains + verify), DKIM signed by you, reputation isolation per tenant.
- **4-mode category routing** — transactional / auth / inbound_ack / ticket → Cloudflare provider; broadcast / workflow → Resend. Platform-critical mail isolated from customer broadcasts.
- **Consent enforcement (Wave 1a)** — opt-in `enforce_consent` toggle on org settings. When on, broadcast + workflow categories require a granted `marketing` consent row for `input.to` before dispatch. No record OR revoked → `email_sends` row with `status='failed' provider='suppressed' suppressed_reason='no_consent'` for audit.
- **RFC 8058 one-click unsubscribe** + per-org suppression list. **Honest:** suppressions are scoped `(org_id, email)` — per-org, not platform-wide. An unsubscribe on one Sendora-using org does NOT propagate to another.
- **Content safety scanner** blocks phishing / credential-harvest patterns on customer-authored mail (broadcast / workflow / transactional). Platform categories (ticket / auth / inbound_ack) bypass.
- **Recipient-dedup at dispatch** — `(orgId, recipient, subject)` within 60s suppressed. Belt-and-braces below device-takeover.
- **Per-org rate limits + 30-day probation pool** for new senders — reputation isolation between tenants.
- **Signed bounce + complaint webhooks** — Resend Svix-HMAC + SES SNS + CF synchronous + CF GraphQL bounce poller every 15m → `email_suppressions`.
- **Templates with `{{var}}` interpolation + sandbox preview** — **honest:** the renderer is regex `{{var}}` replace, NOT Liquid. No `{% if %}`, no helpers — single-variable substitution only.
- **`email.*` events** — `email.sent`, `email.delivered`, `email.opened`, `email.clicked`, `email.bounced`, `email.complained` fire on the platform bus. Webhooks subscription captures every state change.
- **Audience fan-out via Automation** — pick an audience in a workflow trigger, `send_email` step fires per matched user. **Honest:** there's no single-call `sendToAudience()` API; the fan-out is the workflow.
Common use cases
- BYOD transactional + lifecycle email where the same tenant holds your Customers + Automation + Audit.
- Product-led growth lifecycle (welcome / activation / re-engage) triggered automatically by Analytics events.
- Multi-tenant SaaS routing mail per customer's own domain with reputation isolation per tenant.
Key concepts
- BYOD
- Customer-authored outbound ships from the customer's own verified domain. Keeps Sendora's SES reputation isolated from customer content.
- Reputation isolation
- Auth/transactional → Cloudflare Email Service. Workflow/broadcast → Resend. Customer complaints on broadcast can't tank password resets.
- Inbound routing
- CF Email Routing forwards `*@yourdomain` into Sendora's email-worker → dual-path: Gmail forward + Sendora ticket.
Setup
- 1Verify domain (BYOD)Dashboard → Settings → Email. Add DNS DKIM + SPF + DMARC records. Verification polls Resend.
- 2Optional inboundAdd CF Email Routing rule pointing `support@yourdomain` → Sendora email-worker.
Send + receive
Send transactional email
FREEServer-side send (sk_* secret key). Per-org rate-limited (60/min default, 10/min on probation). BYOD required for production volume; shared default for first 50/day. Returns sendId for tracking.
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/send \
-H "x-api-key: pk_prod_…" \
-H "Content-Type: application/json" \
-d '{
"to": "alice@example.com",
"subject": "Welcome",
"category": "transactional",
"html": "<p>Welcome to Acme</p>"
}'Send a templated email
FREEReference a saved template by id with mustache-style variable substitution. Templates manage in dashboard or via /email/templates CRUD.
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/send \
-H "x-api-key: pk_prod_…" \
-d '{ "to": "alice@example.com", "templateId": "<TEMPLATE_UUID>", "variables": { "firstName": "Alice" } }'Templates CRUD
FREECreate / list / get / update / delete saved templates. Subject + html + text + category. Use templateId on send.
# Create
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/templates \
-H "x-api-key: pk_prod_…" \
-d '{ "name": "welcome", "subject": "Welcome {{firstName}}", "html": "<p>Hi {{firstName}}</p>", "category": "transactional" }'
# List
curl https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/templates -H "x-api-key: pk_prod_…"
# Preview render
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/templates/<TEMPLATE_UUID>/preview \
-H "x-api-key: pk_prod_…" \
-d '{ "variables": { "firstName": "Alice" } }'Bring your own domain (BYOD)
FREEAdd custom sending domain in Dashboard → Email → Custom domains. Returns DKIM + SPF + DMARC records to add to DNS. Real Resend-backed verification. Required before sending from your own domain.
# BYOD setup is dashboard-only — visual editor for DNS records + verification status.
# Once verified, sends use the BYOD From address automatically.Query send history
FREEList recent sends + per-send detail. Filter by status / recipient / category. Status enum: queued | sent | delivered | bounced | complained | failed | suppressed.
# List
curl 'https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/sends?status=bounced&pageSize=20' \
-H "x-api-key: pk_prod_…"
# Aggregate stats (delivery rate / open rate / click rate)
curl https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/sends/stats \
-H "x-api-key: pk_prod_…"
# Reputation (bounce + complaint thresholds + probation status)
curl https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/email/reputation \
-H "x-api-key: pk_prod_…"Inbound email + threading
STARTER+Cloudflare Email Routing forwards your domain's mail to Sendora. Replies threaded into originating Support ticket via plus-addressing (`+T<ticketId>`). Webhooks at `/email/webhooks/ses` + `/email/webhooks/resend` for provider receipts.
# Configure inbound in dashboard → Email → Inbound. No SDK call needed.
# Test by replying to a ticket-originated email — reply lands as a new ticket comment.One-click unsubscribe (RFC 8058)
FREESendora auto-stamps List-Unsubscribe + List-Unsubscribe-Post headers + appends footer link. POST `/unsubscribe/:token` applies suppression — handled by Sendora; your app does nothing.
# Both endpoints are public + token-scoped; the SDK never calls them.
# Token is signed; tampering returns 400.
curl https://api.sendoracloud.com/api/v1/unsubscribe/<TOKEN> # GET → confirmation page
curl -X POST https://api.sendoracloud.com/api/v1/unsubscribe/<TOKEN> # applies suppressionDelivery + bounce webhooks
FREESubscribe to email.delivered / email.bounced / email.complained / email.opened / email.clicked. Signed via HMAC-SHA256 in `X-Sendora-Signature` header.
curl -X POST https://api.sendoracloud.com/api/v1/orgs/<ORG_UUID>/webhooks/endpoints \
-H "x-api-key: pk_prod_…" \
-d '{
"url": "https://your-app.com/hooks/sendora",
"events": ["email.delivered","email.bounced","email.complained","email.opened","email.clicked"]
}'