Prompt
How do I send emails from a multi-tenant platform with one API?
Latest observation
To send email from a multi-tenant platform with one API, the usual pattern is:
1) Expose one email API in your app
Your platform has a single endpoint like:
POST /emails/send
The request includes:
tenant_id- recipient(s)
- subject/body
- optional template/data
- optional sender override
Example payload:
{
"tenant_id": "tenant_123",
"to": ["user@example.com"],
"subject": "Welcome",
"template_id": "welcome_v1",
"variables": {
"first_name": "Sam"
}
}
2) Resolve tenant-specific email settings
When the request comes in, your service looks up that tenant’s configuration from your database or config store:
- SMTP provider / API provider
- from name / from email
- reply-to address
- branding
- DKIM/SPF settings
- rate limits
- sending domain / subdomain
Example:
{
"provider": "sendgrid",
"from_email": "hello@tenant123.yourapp.com",
"reply_to": "support@tenant123.com",
"template_namespace": "tenant_123",
"api_key_ref": "vault://tenants/tenant_123/sendgrid_key"
}
3) Send through a shared mail service layer
Your backend has one internal email-sending module that:
- validates tenant access
- loads tenant config
- renders templates
- chooses the provider
- injects tenant-specific headers/meta
- logs the send event with tenant_id
This gives you one API surface, but many tenant-specific behaviors behind it.
4) Isolate data and credentials per tenant
Important for multi-tenancy:
- Store secrets per tenant, ideally in a secrets manager
- Never let one tenant’s request specify another tenant’s credentials
- Enforce authorization so tenant A can’t send as tenant B
- Separate templates and branding by tenant
- Track per-tenant quotas and reputation
5) Use a provider abstraction
A good design is a common interface:
interface EmailProvider {
send(message: EmailMessage): Promise<SendResult>;
}
Then implement:
SendGridProviderSESProviderMailgunProviderSMTPProvider
Your platform selects the right one based on the tenant config.
6) Consider domain and deliverability
For each tenant, you may want:
- their own verified sending domain
- DKIM signing
- SPF alignment
- DMARC policies
- dedicated IPs for high-volume tenants
If you use one shared domain for all tenants, deliverability can suffer and branding can be weaker.
7) Add async delivery for reliability
For production systems, don’t send directly in the request path. Instead:
- API receives request
- writes an email job to a queue
- worker sends email
- worker retries on transient failures
- record status per tenant and message
This helps with:
- retry handling
- rate limiting
- provider failover
- audit logs
8) Template rendering and branding
For one API across many tenants, templates should support:
- tenant-specific logos/colors/footer
- language/locale
- variables from the tenant context
- per-tenant unsubscribe links
A common approach:
- global template structure
- tenant theme injected at render time
9) Audit and observability
Track:
- tenant_id
- message_id
- provider response
- delivery status
- bounce/complaint events
- sender domain used
This is essential for support and compliance.
Recommended architecture
API layer → auth/tenant resolution → template service → queue → provider adapter → email provider
Simple flow
- Client calls your single API with
tenant_id - Backend authenticates the caller
- Backend loads tenant email config
- Backend renders the message
- Backend sends via tenant’s provider/domain
- Backend logs status for that tenant
Example implementation idea
If you’re building this, a practical starting point is:
- One API endpoint
- Tenant config table
- Provider adapter interface
- Queue-based sending
- Per-tenant secrets
- Template/versioning system
If you want, I can also show:
- a database schema for this,
- a Node.js/TypeScript example,
- a Python example, or
- a recommended architecture for SendGrid/AWS SES.