Multi-tenant transactional email

White-label SaaS emails,
without sub-accounts.

Pass a tenantId in the event payload. Logo, primary colour, support email and reply-to address resolve at render time. One template per email type — every customer of your SaaS sees their own brand.

No Postmark server-token-per-tenant. No SendGrid sub-account-per-customer. One API key, one template, N tenants.

  • 1 template, N tenants
  • Render-time variable resolution
  • GDPR-ready
  • Hosted in the EU

// the difference

One field instead of a fan-out.

On the left: what the legacy approach looks like — pick a server token per tenant, look up the right template ID, hope nothing drifts. On the right: what else.events does — one POST, the renderer reads tenant.brand.* at send-time.


// One server token + one template ID per tenant.
// Map lives in your DB or env. Drift is a question of when, not if.
const tenant = await db.tenants.findById(tenantId);
const client = new Postmark.ServerClient(tenant.postmarkServerToken);

await client.sendEmailWithTemplate({
  TemplateId: tenant.welcomeTemplateId,
  From: tenant.fromAddress,
  To: user.email,
  TemplateModel: {
    brand_logo_url: tenant.logoUrl,
    brand_color: tenant.primaryColor,
    user_first_name: user.firstName,
  },
});

// One API key. One template. tenantId resolves brand vars at render.
await fetch("https://app.else.events/api/events", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ELSE_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    eventId: crypto.randomUUID(),
    eventName: "user.signed_up",
    occurredAt: new Date().toISOString(),
    payload: {
      tenantId: "acme-corp",
      user: { email: user.email, firstName: user.firstName },
    },
  }),
});

The Postmark snippet is a representative pattern, not a quote of any specific Postmark API. The point is the shape of the integration — not the vendor.

// proof

Pick a tenant. Same template, different brand.

The interactive demo below is the same control surface we use in the editor. The header block reads tenant.brand.* — change the active tenant and welcome / invoice / receipt all re-skin in lockstep, with no template fork.

// Live inheritance demo

Pick a brand. Every email updates.

One block, three templates, three tenants. The header block reads the active tenant variables — change the tenant and the same template renders in their colours, with their logo, no fork required.

  • welcome.email
  • invoice.email
  • receipt.email
How it works

Three things to set up. Then never again.

Tenants are a first-class concept in else.events. They live in the workspace, not glued on through tags or custom payload conventions.

  1. 1. Define tenants in the dashboard

    Logo URL, primary colour, support email, reply-to address, custom sending domain (optional). Each tenant gets a stable tenantId you control. Add a tenant once, or sync the whole table from your DB through the API.

  2. 2. Pass tenantId in every event

    Your existing /api/events POST gains one extra field: payload.tenantId. The rule engine sees it; the renderer resolves it. Nothing else in your integration changes.

  3. 3. Reference tenant.brand.* in templates

    MJML blocks read {{tenant.brand.logoUrl}}, {{tenant.brand.primaryColor}}, {{tenant.brand.supportEmail}}. One welcome.email, one invoice.email — they pick up the active tenant at render time. Cycle-detected, depth-limited, tenant-scoped.

Built for

Where multi-tenant transactional email matters.

Four shapes of B2B SaaS where every email needs to look like it came from your customer, not from you.

  • SaaS marketplaces & platforms

    Sellers / vendors on your platform send order confirmations, payout notices, dispute notifications. Each one branded to the seller — not to you.

    order.confirmed → vendor logo + colours

  • Embedded fintech & reseller programs

    Payment links, KYC reminders, invoice emails on behalf of partner banks or resellers. Compliance-friendly: the email reads as the partner's, the audit trail stays yours.

    kyc.reminder → partner letterhead

  • Whitelabel agencies & studios

    Run a single product, sell it to dozens of agencies, each with their own client. Tenants nest cleanly: agency → client. Brand per leaf.

    client.report.ready → agency brand

  • Custom-domain SaaS apps

    Customers point their own subdomain at your app and expect emails from support@theirbrand.com. Per-tenant sending domain handles the DNS dance; the rest is template inheritance.

    user.invited → custom domain

And the data?

Your customers' customers stay out of our database.

Multi-tenancy doesn't multiply the privacy surface. We still don't keep a contact list — the recipient address goes to your configured email provider, then becomes a SHA-256 hash in our delivery log. Tenant metadata (logo, colours, support email) lives in the workspace; end-recipient identity does not.

  • No end-customer contacts stored on our side
  • Per-tenant sending domains supported (BYO DNS)
  • DPA covers your SaaS, not every downstream tenant
Read the privacy details
FAQ

Things you'll want to know.

Do I need a separate sending domain per tenant?

No, but you can. By default everything ships through your workspace's sending domain (or shared Postmark infrastructure during the beta). If a tenant wants their own domain, you add it to the tenant record and verify the DNS — same SPF / DKIM dance as anywhere else, just scoped per tenant.

What happens if a tenant has no logo or colour set?

Templates fall back deterministically: tenant value → workspace default → empty string. Same chain as locale fallback. You won't ship an email with a literal {{tenant.brand.logoUrl}} placeholder — the editor surfaces missing values during dry-run.

How many tenants can I have per plan?

There's no hard cap on tenant count during the public beta — tenants are metadata, not metered resources. Emails are the metered resource. Each plan's monthly email budget covers all tenants together. If you're sending across thousands of tenants, talk to us — Premium is currently the right fit.

Can I create or update tenants via API?

Yes. POST / PATCH / DELETE on /api/tenants accepts the same fields as the dashboard form. Most teams sync the table once a day from their own DB; some create the tenant just-in-time the first time an event references an unknown tenantId.

What if a customer of mine wants to leave — can I export their tenant data?

Tenant config (brand vars, domain settings) is exportable as JSON via the dashboard or API. Their delivery log lives in your workspace, scoped by tenantId; you can filter and export it the same way you'd export any other slice.

Ship branded emails for every tenant. Today.

14-day free trial, no credit card. POST your first multi-tenant event in an afternoon — your existing integration stays as-is, you just add one field.