Per-tenant suppression list
In platform mode, each tenant has its own suppression list. A bounce under tenant A doesn't block tenant B from contacting the same address.
How it works
The suppressions table has nullable tenantId:
tenantId = null— org-wide suppression. Affects every tenant under this org.tenantId = "tnt_X"— tenant-scoped suppression. Affects only sends from keys bound to tenant X.
On each send, Sendoka checks the address against the union: (tenantId = null OR tenantId = <sender's tenant>). If any row matches, the send is blocked (or filtered in batch/audience).
Adding a suppression — tenant-bound key
Keys bound to tnt_X can only write tenant-scoped suppressions:
curl -X POST https://api.sendoka.com/api/v1/suppressions \
-H "Authorization: Bearer sok_live_TENANT_X_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": "user@example.com",
"channel": "email",
"reason": "manual"
}'
The row is inserted with tenantId = "tnt_X".
Adding a suppression — platform-root key
Platform-root keys can choose. Omit tenant_id for org-wide, or pass a tenant id:
# Org-wide (every tenant sees it)
curl -X POST .../suppressions \
-H "Authorization: Bearer sok_live_ROOT_KEY" \
-d '{ "address": "spam@example.com", "channel": "email", "reason": "manual" }'
# Tenant-scoped (only tenant X sees it)
curl -X POST .../suppressions \
-H "Authorization: Bearer sok_live_ROOT_KEY" \
-d '{ "address": "a@example.com", "channel": "email", "reason": "manual", "tenant_id": "tnt_X" }'
Listing
Tenant-bound keys see only their tenant's + null-scoped rows. Root keys see all.
curl 'https://api.sendoka.com/api/v1/suppressions?channel=email&limit=100' \
-H "Authorization: Bearer $KEY"
Response rows include tenant_id so you can tell the scope.
Auto-suppression from bounces
When SES reports a bounce or complaint:
- Sendoka looks up the original message.
- Inserts a suppression with that message's
tenantId(null if platform-root send, tenant id if tenant send). - So a bounce under tenant A doesn't block tenant B's subsequent send to the same address.
This is the isolation behavior most platform customers expect. If you want global suppression (one bounce kills the address across all tenants), override by calling POST /api/v1/suppressions with a root key and tenant_id omitted.
Removing
Same scoping. Tenant-bound keys can only delete rows with their tenantId.
curl -X DELETE .../suppressions/user@example.com \
-H "Authorization: Bearer $KEY"
Gotchas
- Tenants share the underlying SES bounce rate. Isolation protects your customer's lists, not your AWS account. One customer with a bad list still threatens your SES reputation.
- Complaints should stay suppressed forever. Don't script around them.
- The
channelcolumn is indexed. Filter aggressively when listing.