Sending & Testing

Setting up MailBlastr for multi-tenant apps

Two patterns for SaaS apps that send on behalf of tenants: a single MailBlastr account with domain-scoped keys, or separate accounts (BYOK). Trade-offs in isolation, billing, and deliverability.

Many SaaS platforms need to send email on behalf of their tenants — transactional notifications, onboarding sequences, or campaigns from a tenant’s own domain. There are two main ways to configure MailBlastr for this. Neither is universally "right"; the best choice depends on how much control you want and what your tenants need.

At a glance

FactorSingle accountSeparate accounts / BYOK
Setup complexityLow — fully API-drivenHigher — manual account per tenant
Sending isolationShared — one bad actor affects allFull — each tenant isolated
BillingOne plan covers all tenantsEach tenant manages their own plan
Per-tenant analyticsNot nativeEach tenant has its own dashboard
Webhook routingVia tags or from domainEach account has its own webhooks
DeliverabilityShared sender reputationIndependent sender reputation
Rate limitsAggregate — likely needs an increaseEach tenant uses their own limits

Option A: a single MailBlastr account

You manage everything from one account: add each tenant’s domain, verify it, and mint a domain-scoped API key so each tenant can only send from their own domain. All sending, billing, and analytics flow through your account. The whole flow is API-driven.

When a tenant signs up, create and verify their domain.

1. Create the tenant's domain:

import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.domains.create({ "name": "tenant-domain.com" });
console.log({ data, error });

2. After the tenant publishes the DNS records, trigger verification:

import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.domains.verify('DOMAIN_ID');
console.log({ data, error });
Verification is asynchronous — DNS must propagate first. Listen for the `domain.verified` webhook instead of polling.

Once verified, create a sending_access key scoped to that domain so the tenant can only send from it:

import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.apiKeys.create({
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
});
console.log({ data, error });
The API key token is shown only once at creation. Store it securely — you cannot retrieve it again. See Create API key.
  • Pros: seamless API-driven setup; domain-scoped keys confine each tenant to their own domain; one account to manage.
  • Cons: no per-tenant analytics in the dashboard; tenants share your sender reputation; aggregate volume will likely need a quota increase.
Shared reputation cuts both ways: if one tenant sends spam or runs up bounce/complaint rates, deliverability degrades for every tenant on your account — and in severe cases the whole account can be suspended.

Option B: separate accounts (BYOK)

In the Bring-Your-Own-Key model, each tenant creates their own MailBlastr account, adds their domain, and gives you their API key. Your app stores each tenant’s key and uses it when sending for them:

import { MailBlastr } from 'mailblastr';

// Look up the calling tenant's own MailBlastr key, then send with it.
const tenantApiKey = await getTenantKey(tenantId);
const mb = new MailBlastr(tenantApiKey);

await mb.emails.send({
  from: 'notifications@tenant-domain.com',
  to: ['user@example.com'],
  subject: 'Your order has shipped',
  html: '<p>Your order #1234 is on its way.</p>',
});
  • Pros: full isolation — each tenant’s reputation and deliverability are independent; no liability for tenant behavior on your account; each tenant has their own dashboard and rate limits.
  • Cons: each tenant must create and manage their own account and plan, which adds onboarding friction for those unfamiliar with email infrastructure.

Webhook routing

With Option A, attach a tenant identifier as a tag when sending; tags are echoed in webhook payloads, so you can route events back to the right tenant:

import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.emails.send({
  "from": "notifications@tenant-domain.com",
  "to": ["user@example.com"],
  "subject": "Welcome aboard",
  "html": "<p>Thanks for signing up.</p>",
  "tags": [{ "name": "tenant_id", "value": "tenant_abc123" }]
});
console.log({ data, error });

With Option B, each tenant configures their own webhooks, so events are naturally isolated — no tagging needed.

Deliverability & migration

Sender reputation is tied to the sending account and its domains. Under Option A all tenants share one reputation; under Option B each is independent. Either way, every new tenant domain should follow a warm-up schedule and ideally send from a subdomain to protect the tenant’s root domain.

Moving a domain between accounts requires deleting it from the original account before re-verifying it in the second — plan for a sending interruption during DNS propagation.

See Create domain, Create API key, and Webhooks for the underlying endpoints.