Managed customers
Build domain onboarding directly into your own SaaS product. Your customers stay in your interface while your backend creates isolated customer records, returns DNS instructions, and manages delivery through Sendix.
Integration model
A managed customer represents one downstream organization, shop, project, or account in your product. It is not a Sendix user and never needs a Sendix login.
- Your customer identityexternal_id stores the stable primary key from your own database.
- Server-to-server APIYour backend calls Sendix with a scoped key; never expose it in a browser or mobile app.
- Safe retriesCreating the same external_id again returns the original customer with idempotent: true.
- Workspace isolationCustomers, domains, plan limits, and credentials remain isolated to the owning workspace.
Authentication
Create an API key in the Sendix dashboard and choose Customer platform. This grants tenant:read, tenant:manage, domain:read, and domain:manage. Use a separate email:send key for sending when you want independent rotation and audit boundaries.
Authorization: Bearer $SENDIX_API_KEYCreate a customer
Use the immutable id from your own system as external_id. Use inherit unless this customer must explicitly use Sendix delivery. Metadata is returned to your integration but does not affect routing.
curl -X POST https://sendix.dev/api/v1/managed-tenants \
-H "Authorization: Bearer $SENDIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "shop_10294",
"name": "Acme Store",
"provider": "inherit",
"metadata": {
"shopify_domain": "acme.myshopify.com",
"environment": "production"
}
}'Response
{
"id": "8ee3cb1c-7b87-45af-b556-d9be311b29ec",
"external_id": "shop_10294",
"name": "Acme Store",
"status": "active",
"provider": "inherit",
"domain_count": 0,
"metadata": {
"shopify_domain": "acme.myshopify.com",
"environment": "production"
},
"idempotent": false
}The first request returns 201. A retry with the same external_id returns 200, the original customer, and idempotent: true. Resolve a customer later without storing its Sendix id:
curl https://sendix.dev/api/v1/managed-tenants/by-external-id/shop_10294 \
-H "Authorization: Bearer $SENDIX_API_KEY"Add a domain
Register the exact sending domain the customer will use. The response includes the DNS records you should render in your own onboarding UI. Store both the customer id and domain id for later verification and cleanup.
curl -X POST https://sendix.dev/api/v1/managed-tenants/$TENANT_ID/domains \
-H "Authorization: Bearer $SENDIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "mail.acme.com" }'Example response
{
"id": "c3a1e2b3-89a8-4ad8-97f9-b929f1d86d0b",
"domain": "mail.acme.com",
"verified": false,
"managed_tenant_id": "8ee3cb1c-7b87-45af-b556-d9be311b29ec",
"records": [
{ "type": "TXT", "name": "mail.acme.com", "value": "v=spf1 ..." },
{ "type": "TXT", "name": "..._domainkey.mail.acme.com", "value": "v=DKIM1; ..." }
]
}Verify and send
After the customer publishes the records, call the standard domain verification endpoint. Keep showing a pending state until verification is healthy. Sending from the verified domain automatically resolves its managed customer and provider routing; the email payload does not need a separate tenant id.
# Refresh DNS verification after your customer publishes the records
curl -X POST https://sendix.dev/api/v1/domains/$DOMAIN_ID/verify \
-H "Authorization: Bearer $SENDIX_API_KEY"
# Send from the verified customer domain
curl -X POST https://sendix.dev/api/v1/emails \
-H "Authorization: Bearer $SENDIX_SENDING_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_78421_confirmation" \
-d '{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Order confirmed",
"html": "<p>Your order is confirmed.</p>"
}'Webhooks and events
Configure webhooks on the workspace API boundary, then route events to the customer record in your own database. The webhook secret is returned only when the webhook is created, so store it server-side and never expose it to a browser.
curl -X POST https://sendix.dev/api/v1/webhooks \
-H "Authorization: Bearer $SENDIX_WEBHOOK_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/sendix/events",
"events": ["domain.verified", "email.sent", "email.delivered", "email.bounced", "email.received"]
}'
curl -X POST https://sendix.dev/api/v1/webhooks/$WEBHOOK_ID/test \
-H "Authorization: Bearer $SENDIX_WEBHOOK_KEY"X-Sendix-Signature against the raw request body and reject stale timestamps. Use the domain or resource id in the event to map it to your own customer.Lifecycle and errors
| Code | Meaning | What to do |
|---|---|---|
| managed_tenants_plan_required | Feature is not in the plan. | Upgrade or enable the plan entitlement. |
| managed_tenant_limit_reached | Customer quota is full. | Upgrade or remove an unused customer. |
| managed_tenant_domain_limit_reached | Per-customer domain quota is full. | Upgrade or remove a domain. |
| managed_tenant_paused | The customer is paused. | Resume it before adding domains or sending. |
| managed_tenant_has_domains | Delete was attempted too early. | Remove every attached domain first. |
| managed_tenant_not_found | The id is outside this workspace or missing. | Check the external id mapping. |
- Pause before destructive changesPause the customer, stop new traffic, remove domains, then delete the customer.
- Retry only safe operationsCustomer creation is idempotent by external_id; use Idempotency-Key separately for email sends.

