Skip to content
DocsPlatform API

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.
Recommended architecture
Browser or mobile app → your backend → Sendix API. Return only the DNS records and status fields your customer needs; keep the Sendix API key in your server-side secret store.

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_KEY
Domain-restricted keys
A platform integration normally creates domains dynamically, so use “All domains” for its customer-platform key. A key restricted to existing domain ids cannot anticipate a domain that has not been created yet.

Create 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; ..." }
  ]
}
Do not rewrite DNS values
Display record type, name, value, and priority exactly as returned. DNS providers differ in how they display the zone suffix, but the API response remains the source of truth.

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"
Verify every event
Verify 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

CodeMeaningWhat to do
managed_tenants_plan_requiredFeature is not in the plan.Upgrade or enable the plan entitlement.
managed_tenant_limit_reachedCustomer quota is full.Upgrade or remove an unused customer.
managed_tenant_domain_limit_reachedPer-customer domain quota is full.Upgrade or remove a domain.
managed_tenant_pausedThe customer is paused.Resume it before adding domains or sending.
managed_tenant_has_domainsDelete was attempted too early.Remove every attached domain first.
managed_tenant_not_foundThe 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.