Multi-tenant architectures on Sent: profiles vs. accounts
This guide shows you how to choose and set up a tenant model for a platform or ISV that sends messages on behalf of multiple customers. Sent's primary multi-tenancy mechanism is Sender Profiles: isolated messaging identities inside one organization, provisioned through the API. A single shared account and fully separate accounts remain workable alternatives at the two ends of the isolation spectrum, and both are covered below with their constraints.
It assumes you can already send messages. The profile-based setup additionally requires an organization account (GET /v3/me returns "type": "organization") and an API key whose user has the admin role. See roles and permissions.
Compare the three tenant models
| Sender Profiles | One shared account | Separate accounts | |
|---|---|---|---|
| Isolation | Contacts and templates are always dedicated per profile; the identity, campaign, and billing are inherited or dedicated | None (every tenant shares all resources) | Complete (nothing shared) |
| Tenant provisioning | API: POST /v3/sender-profiles, one call | None needed | Manual: each tenant signs up for their own account, and the API has no account-creation endpoint |
| Billing | Organization, dedicated, or dedicated with organization fallback (billing) | One bill to you | Each tenant pays Sent directly |
| Credentials | Per-profile API key, or one organization key scoped with x-profile-id | One API key | One API key per tenant, managed by you |
| Rate limit pool | A separate pool per profile key; requests scoped via x-profile-id draw from the organization's pool | One pool shared by all tenants | A separate pool per account |
To pick a model:
- If you onboard tenants programmatically, or tenants need a dedicated US A2P (TCR) registration, a dedicated sender, or a WhatsApp presence of their own, use Sender Profiles. Isolated contact lists and templates come with the model rather than being a reason to choose it.
- If every tenant sends the same kind of content under your platform's single brand and sender identity, a shared account is enough, provided you accept the shared rate limit pool and build tenant attribution yourself.
- If tenants must own their Sent relationship end to end (their own login and billing relationship, no shared organization), use separate accounts and treat each one as an independent integration.
Build on Sender Profiles
This is the recommended model: one organization, one profile per tenant. The full provisioning walkthrough with every field, status, and failure mode is Create and activate Sender Profiles via the API; the sequence below is the architecture-level view.
Provision a profile per tenant
One call. name and short_name are the only required fields; billing says who pays, and a channels.sms block says whether the tenant gets a dedicated sender:
curl -X POST "https://api.sent.dm/v3/sender-profiles" \
-H "x-api-key: $ORG_API_KEY" \
-H "Idempotency-Key: create-profile-tenant-42" \
-H "Content-Type: application/json" \
-d '{
"name": "Tenant 42 Coffee Co",
"short_name": "T42COFFEE",
"description": "Sender profile for tenant 42",
"billing": { "inherit": true }
}'billing: {"inherit": true} keeps charges on your platform's bill; omit the block to bill the tenant directly. Contacts and templates stay isolated to the tenant either way — there is nothing to configure and no way for one tenant to see another's.
There is no completion step and no callback to receive: the profile is usable as soon as it is created, inheriting your identity and terminating through the shared routes for its country. Give it a dedicated sender, and register its compliance, with POST /v3/channels/sms when the tenant needs one. The response carries the profile's api_key, shown once and never returned again.
Route each tenant's traffic
Two options, usable side by side:
- Per-profile API keys: store one key per tenant and instantiate the client with it. Each profile key gets a separate 10,000 requests-per-minute rate limit pool: one tenant's burst cannot starve another's.
- One organization key +
x-profile-id: keep a single credential and scope each request to a profile by UUID. All scoped requests draw from the organization's rate limit pool.
curl -X POST "https://api.sent.dm/v3/messages" \
-H "x-api-key: $ORG_API_KEY" \
-H "x-profile-id: $PROFILE_ID" \
-H "Content-Type: application/json" \
-d '{"to": ["+15551234567"], "template": {"id": "tmpl_123"}}'Only organization keys may send x-profile-id; profile keys are rejected with 403, and a profile outside your organization returns 404. Per-language client code for both patterns is in Integrating Sender Profiles into Your Application.
Attribute webhook events to tenants
Webhook events do not carry your tenant identifiers. Persist the message_id values from each send response against the tenant's profile, then resolve incoming events through that mapping. The receiver code is in Track usage per profile in webhooks; the envelope is documented in the events reference.
Isolation is per resource, not all-or-nothing: contacts, templates, and the sender are always dedicated to the profile, while the brand, the campaign, and billing can be inherited from your organization. See the governance model for what each resource does.
Alternative: one shared account
If all tenants message under your platform's brand, with the same sender identity, the same templates, and no per-tenant compliance registration, you can run everything through a single account.
The send request has no tenant or metadata field, so tenant attribution is entirely your app's job. Record the mapping when you send, using the message_id values the API returns:
const response = await client.messages.send({
to: recipients,
template: { id: templateId }
});
// The request body carries no tenant identifier — persist the mapping yourself
await db.messages.insertMany(
response.data.recipients.map((r) => ({
sentMessageId: r.message_id,
to: r.to,
tenantId: tenant.id
}))
);Webhook handlers then look up the tenant by payload.message_id, exactly as in the per-profile attribution pattern.
Constraints to plan for:
- All tenants share one 10,000 requests-per-minute pool. The ceiling is high enough that a single tenant is unlikely to starve the others, but one runaway tenant can still consume it. Enforce per-tenant quotas in your own app. See How to handle Sent API rate limits.
- Every tenant sends from the same numbers, templates, and TCR registration; a compliance problem caused by one tenant affects all of them.
- If you later need isolation, you can move a tenant onto a Sender Profile without leaving your organization. That migration path is the main argument for starting with profiles even when sharing would work today.
Alternative: separate accounts per tenant
If each tenant must be Sent's customer of record (their own login, billing, and compliance standing, and no shared organization), give each tenant a separate account. There is no API for creating accounts, so each tenant signs up through the dashboard themselves; your platform stores their API key and treats each account as an independent integration:
// One secrets-manager entry per tenant; a leaked key exposes one tenant, not all
const client = new SentDm({
apiKey: await secrets.get(`sent-api-key-${tenant.id}`)
});Each account has its own rate limit pool and its own bill. The costs are operational: onboarding cannot be automated, nothing (templates, contacts, registration) can be shared, and there is no organization-level view across tenants. Before choosing this model, check whether a Sender Profile with dedicated billing and a dedicated sender already gives the tenant what they actually need (direct billing and full data isolation) without giving up API provisioning.
Verify your setup
- Send a test message as one tenant (their profile key,
x-profile-idscope, or their account key) and confirm the202response, then confirm the delivery events for thosemessage_idvalues resolve to the same tenant in your database. - For profile-based setups, list templates with tenant A's key and confirm you see only that profile's templates, never tenant B's.
- For shared-account setups, confirm your per-tenant quota enforcement triggers before the account-wide limit does: a tenant at its quota should be throttled by your app, not by a
429that affects every tenant.
Related pages
- Sender Profiles: the inheritance and governance model behind profile-based multi-tenancy
- Create and activate Sender Profiles via the API: the full provisioning walkthrough
- Integrating Sender Profiles into Your Application: per-tenant sending and webhook attribution in eight languages
- Creating a Sender Profile: the dashboard flow
- Rate Limits: pool scoping rules for accounts, profiles, and
x-profile-id
API v2 to v3 Migration
Migrate your integration from the legacy Sent v2 API to v3 and update auth headers, request and response envelopes, error codes, webhooks, and idempotency.
Create and activate Sender Profiles via the API
Create Sender Profiles with the Sent API: the minimum request, which resources are dedicated versus inherited, giving a profile a sender, and reading what its compliance still owes.