Sequencer, Unibox and CRM are live:send, reply and close from the same login, included on every plan
Use case · By team

Cold email infrastructure for your SaaS product

Embed cold email in your SaaS with the EmailPal API: tenant tags, bulk provisioning, webhooks. About $7 to $10 a month per customer at 5 mailboxes each.

The short answer

You can embed cold email in your own SaaS product by calling the EmailPal API from your backend, under your own account and name, which section 9 of the Terms allows. At five mailboxes per customer with warming, the infrastructure costs about $7 to $10 per customer per month: Starter ($69) covers 10 customers, Growth ($189) covers 40 and Scale ($499) covers 120, plus a one-time domain cost. You tag every domain and mailbox with tenant:<id> and build customer isolation, inbox views, billing and abuse handling yourself. EmailPal has no client workspaces, per-customer logins or white-label.

Your app owns the customer relationship: your auth, your billing, your UI. One EmailPal account sits behind it and sees one write key. Provision with POST /domains (50 per call) and POST /mailboxes (100 per call), tag everything with tenant:<id>, send an Idempotency-Key on every create, and use webhooks rather than polling because the account has 1,000 API requests an hour. Mailboxes beyond the plan are $1/mo each up to a hard ceiling of three times the included count, which is 1,800 on Scale. The cost of the model is on you: you carry customer isolation, per-customer billing and abuse handling, and one customer’s bad sending can trigger enforcement against the whole account.

How it works, step by step

  1. Step 1

    Decide what your customers see and what EmailPal sees

    Your customers see your product only. They authenticate to you, pay you, and never receive an EmailPal key, which the Terms forbid sharing between organisations. EmailPal sees one account and one key, never your customers. Anything per-customer, such as quotas, inbox access and invoices, has to exist in your own database.

  2. Step 2

    Create separate keys by scope

    Scopes are hierarchical: read, write, admin. Give the provisioning service a write key. Only the service that exports mailbox credentials or tops up the domain balance should hold an admin key. A write key cannot read mailbox passwords.

  3. Step 3

    Store the customer map yourself and tag every resource

    When POST /domains returns domain_id and POST /mailboxes returns mailbox ids, save them against your customer id. Also send tags: ["tenant:<id>"] on the create so the account carries the same fact. Tags are stored and filterable with ?tag= but not interpreted, so treat them as a recovery path and a reporting filter, not as your access control.

  4. Step 4

    Register one webhook endpoint and verify every delivery

    POST /webhooks with your URL and, ideally, all events. The response returns the signing secret once. Verify the EmailPal-Signature header, reject timestamps more than five minutes old, dedupe on the event id because delivery is at-least-once, and answer 2xx within ten seconds before doing the work. An account can have ten endpoints.

  5. Step 5

    Onboard a customer: domains first, mailboxes when the domain is ready

    Buy the customer’s domains with their tag and an idempotency key derived from your own ids. Do not create mailboxes in the same request flow: a domain must be ready or active first, otherwise POST /mailboxes returns 409 domain_not_ready. Wait for the domain.ready webhook, then create four to six mailboxes on it.

    Buy domains for a customerts
    const BASE = 'https://www.emailpal.io/api/public/v1';
    
    async function emailpal<T>(
      method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
      path: string,
      body?: unknown,
      idempotencyKey?: string,
    ): Promise<T> {
      const res = await fetch(BASE + path, {
        method,
        headers: {
          Authorization: 'Bearer ' + process.env.EMAILPAL_API_KEY, // write scope
          'Content-Type': 'application/json',
          ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
        },
        body: body === undefined ? undefined : JSON.stringify(body),
      });
      if (!res.ok) {
        const payload = await res.json().catch(() => null);
        throw new Error(payload?.error?.code ?? 'http_' + res.status); // branch on error.code
      }
      return (await res.json()) as T;
    }
    
    export async function buyDomainsForCustomer(customerId: string, names: string[]) {
      const result = await emailpal<{
        queued: { domain: string; domain_id: string; job_id: string; price_cents: number }[];
        rejected: { domain: string; reason: string }[];
        balance_cents: number;
      }>(
        'POST',
        '/domains',
        { mode: 'purchase', domains: names, years: 1, tags: ['tenant:' + customerId] },
        'cust-' + customerId + '-domains-1', // 8 to 255 characters, same key + same body = replay
      );
    
      for (const d of result.queued) {
        await saveDomain(customerId, d.domain_id, d.domain, d.job_id); // your table
      }
      return result;
    }
    
    declare function saveDomain(customerId: string, domainId: string, name: string, jobId: string): Promise<void>;
  6. Step 6

    React to webhooks to create mailboxes and route mail

    On domain.ready, look up the customer for domain_id and create their mailboxes with the same tag. On mailbox.provisioned, mark the mailbox usable in your product. On message.received, the payload carries mailbox_id, so map it to the customer and fetch the body from GET /inbox/{message_id}. reply.received carries the campaign and lead but not the mailbox, so fetch the message to find it.

    Webhook handler (shape, not production code)ts
    import { createHmac, timingSafeEqual } from 'node:crypto';
    
    function verify(raw: string, header: string, secret: string): boolean {
      const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]));
      const t = Number(parts.t);
      if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
      const expected = createHmac('sha256', secret).update(t + '.' + raw).digest('hex');
      const a = Buffer.from(expected);
      const b = Buffer.from(parts.v1 ?? '');
      return a.length === b.length && timingSafeEqual(a, b);
    }
    
    export async function POST(request: Request) {
      const raw = await request.text(); // compare against the raw body, before JSON.parse
      if (!verify(raw, request.headers.get('emailpal-signature') ?? '', process.env.EMAILPAL_WEBHOOK_SECRET!)) {
        return new Response('bad signature', { status: 400 });
      }
      const event = JSON.parse(raw) as { id: string; type: string; data: Record<string, any> };
      if (await alreadyProcessed(event.id)) return new Response('ok'); // at-least-once delivery
    
      if (event.type === 'domain.ready') {
        const customerId = await customerForDomain(event.data.domain_id);
        await emailpal('POST', '/mailboxes', {
          domain_id: event.data.domain_id,
          count: 3,
          pattern: 'first.last',
          tags: ['tenant:' + customerId],
        }, 'cust-' + customerId + '-mailboxes-' + event.data.domain_id);
      }
    
      if (event.type === 'message.received') {
        const customerId = await customerForMailbox(event.data.mailbox_id);
        await queueForCustomer(customerId, event.data.message_id); // fetch /inbox/{id} later
      }
    
      await markProcessed(event.id);
      return new Response('ok'); // 2xx within ten seconds
    }
    
    declare function alreadyProcessed(id: string): Promise<boolean>;
    declare function markProcessed(id: string): Promise<void>;
    declare function customerForDomain(domainId: string): Promise<string>;
    declare function customerForMailbox(mailboxId: string): Promise<string>;
    declare function queueForCustomer(customerId: string, messageId: string): Promise<void>;
  7. Step 7

    Serve each customer only their own mail, and meter them yourself

    GET /inbox filters by mailbox_id, not by tag, and a key sees the whole account. Every call your backend makes for a customer must first check that the mailbox or domain id belongs to that customer in your database, then pass it through. Count each customer’s sends and mailboxes against caps you set, and keep the sum of those caps at or below what you have bought.

  8. Step 8

    Watch for abuse and be ready to pause

    Poll GET /mailboxes/{id} for sending_pause, subscribe to campaign.paused and read enforcement on GET /account. To stop a customer, PATCH their mailboxes with paused: true, which queues a job. Deleting a mailbox is irreversible and discards its warming history, so pause first.

01

The architecture

Your customer uses your product. Your backend authenticates them, checks their plan, and calls EmailPal with one server-side key. EmailPal provisions and warms the infrastructure and tells you what happened by webhook. All of the customer-facing surface is yours: the domain, the UI, the keys, the support, the invoice.

This is the same pattern as reselling email verification, applied to mailboxes. Terms section 9 describes it directly: you may embed mailbox and domain provisioning in your own product and supply it to your own end customers, through the REST API or MCP server, under your own account and your own name. The MCP server runs locally on a machine and there is no hosted MCP endpoint, so a production product calls the REST API.

Request flowtext
Your customer
  -> YOUR APP        (your auth, your plans, your UI, your invoice)
  -> YOUR BACKEND    (customer map, quotas, abuse checks, retries)
  -> EmailPal API    (one write key, one account, 1,000 requests an hour)
       POST /domains     up to 50 per call, tags: ["tenant:<id>"]
       POST /mailboxes   up to 100 per call, tags: ["tenant:<id>"]
       GET  /inbox       by mailbox_id
  <- WEBHOOKS          (domain.ready, mailbox.provisioned, message.received, ...)

02

Tags: what they give you and what they do not

Tags exist on domains and mailboxes. Each is up to 64 characters, a resource can carry up to 20, and GET /domains and GET /mailboxes accept repeated ?tag= parameters that are ANDed, so a narrowing filter can only reduce the result. EmailPal does not parse tenant:<id>; it is a convention. PATCH replaces the whole tag set, so read before you write.

Tags do not appear in webhook payloads. domain.ready carries domain_id and domain; mailbox.provisioned carries mailbox_id, email, domain_id and domain; message.received carries mailbox_id and the mailbox address. Your customer map is therefore the lookup, and tags are how you rebuild it from EmailPal if your own database is ever wrong. The public reference documents tags on domains; the mailbox routes accept and return them as well, so check the live response before relying on them in a client.

03

Idempotency, polling and the hourly budget

Send an Idempotency-Key of 8 to 255 characters on POST /domains and POST /mailboxes. Domain purchases spend the prepaid balance, so a retry after a timeout without the key can buy twice. The same key with the same body replays the stored response for 24 hours and adds Idempotent-Replay: true; the same key with a different body returns 409 idempotency_key_reused. Derive keys from your own ids, such as the customer id and the step, so a restart of your worker repeats the same key.

The hourly request budget is 1,000 on every current plan, in a fixed clock-hour window, and failed requests count. A 429 rate_limited response carries limit, used and resetAt with Retry-After. Polling spends this: a hundred job ids polled every few seconds will exhaust it. Prefer webhooks, list jobs by type or status rather than one id at a time, and cache reads. The account allows ten webhook endpoints, so fan out to customers inside your own system rather than registering one per customer.

Webhook deliveries retry at 30 seconds, 2 minutes, 10 minutes, 30 minutes, 2 hours and 6 hours, then are marked dead. Twenty consecutive failures disable the endpoint until you re-enable it with PATCH, so alert on your own receiver being down.

04

What you have to build yourself

Customer isolation: there is one account, so a key can read and change everything in it. The check that customer A cannot touch customer B’s mailbox lives in your backend, on every route that takes an id.

Per-customer inbox views: the Unibox is a single account-wide inbox with filters for status, campaign and mailbox, and no per-customer login. To show a customer their replies, read GET /inbox per mailbox id, or consume message.received, and render it in your product. Sending a reply is POST /inbox/send.

Billing and metering: EmailPal bills you per mailbox, per warming inbox and per lease on one invoice. It has no per-customer breakdown. Verification is one shared daily allowance for the account, and the API budget is one number, so per-customer quotas for either are yours to enforce.

Credentials: if your product hands customers SMTP and IMAP passwords, those come from GET /mailboxes/export with an admin key, filtered by domain_id or a comma-separated mailbox_ids list. The export has no tag filter. Decide deliberately whether to expose passwords at all or to keep customers on your own sending path.

05

Abuse, compliance and enforcement: you are the responsible party

Under section 9 you must make sure each end user complies with the acceptable use policy and the Terms, and anything an end user does through your account is treated as your act. Under section 5 you are the sender of every message and responsible for the lawful basis of every list, and you warrant that lists were not purchased, rented, scraped, harvested or generated. Your product needs its own sign-up checks and terms that pass these obligations down.

Enforcement lands on the account. Under section 7 the platform may throttle, delay or pause sending from any mailbox, domain, IP or account without prior notice and suspend sending while abuse is investigated. In code, an account with throttled or no_new_mailboxes enforcement cannot add domains or mailboxes at all, and a suspended or closed account is refused. A single customer with a bad list can therefore stop you onboarding the next customer. Complaints and unsubscribes feed a platform-wide suppression list, and under section 8 EmailPal will not remove addresses from it at a customer’s request.

Built-in protections help but do not replace screening. A campaign pauses at 5% hard bounces after 200 sends, a mailbox pauses at 5% rejected mail with a warning at 3%, and GET /mailboxes/{id} reports the pause with counts of cancelled and refused messages. Verification is built in, so require a verified list before a customer launches.

06

Unit economics from the real prices

Assume five mailboxes per customer (one domain, since the mailbox form suggests four to six mailboxes per domain) and warming on. Starter is $69/mo for 50 mailboxes, so 10 customers: $69 plus $30 of warming is $99, or $9.90 each. Growth is $189/mo for 200 mailboxes, so 40 customers: $189 plus $120 is $309, or $7.73 each. Scale is $499/mo for 600 mailboxes, so 120 customers: $499 plus $360 is $859, or $7.16 each. Past the plan, each additional five-mailbox customer is $8.00 a month ($1 overage plus $0.60 warming, times five).

On top: domains are a one-time registration of about $1 to $10 each from the prepaid balance, so roughly $1 to $30 per customer once, quoted exactly by POST /domains/search. A leased pre-warmed inbox is $3/mo with a 90-day minimum and sits outside the plan allowance, which makes it a way to give a customer who needs to send this week an overflow inbox. If you charged $29 a month per customer, an assumption and not a market price, 120 customers on Scale would leave about $21.84 per customer before payment fees, hosting, support and bad debt.

These figures assume every mailbox slot is filled and every customer stays. An empty slot still costs the plan fee. The floor is $69 a month for Starter before you have a single customer.

07

Capacity and the hard multiple

Mailboxes above the included count are billed at $1 each, not refused, until a hard ceiling of three times the included count: 150 on Starter, 600 on Growth, 1,800 on Scale. Past it, POST /mailboxes returns 402 plan_limit and the message says jumps this large need a conversation first. At five mailboxes per customer, that is 30, 120 and 360 customers. The multiple is a default that an operator can change for one account; do not plan on it.

Each extra mailbox also unlocks one matching domain slot, so domain slots follow the mailbox count. Sending capacity at the default 15 per inbox is 75 cold emails a day per five-mailbox customer, and 27,000 a day for 1,800 mailboxes. A customer who asks for more has to add mailboxes and domains, because the hard cap is 100 per inbox. Terms section 9 says capacity above what you have purchased is not guaranteed and requests above it may be refused, so do not promise your customers more than you have bought.

08

What the Terms say

Section 9 of the Terms (effective 6 October 2026) lets you embed mailbox and domain provisioning in your own product and supply it to your own end customers through the REST API or MCP server, under your own account and name, without a separate agreement. API keys must not be shared between organisations (section 10). Reselling access to the dashboard, white-labelling the Service, or using the EmailPal name and brand as your own needs written agreement. This page is a summary, not legal advice; have counsel read the Terms against your product before launch.

The numbers

Limits, prices and names, in one table

The figures this page relies on, so you do not have to hunt for them.

Domains per create call50
Mailboxes per create call100count, personas or named local parts
Hourly API budget1,000 requestsEvery current plan; fixed clock-hour window; failed requests count
Webhook endpoints per account10At-least-once delivery; 20 consecutive failures disable an endpoint
Tag limits64 characters, 20 per resourceUp to 10 in one filter, ANDed
Idempotency-Key8 to 255 characters, 24 hours
Starter plan$69/mo50 mailboxes; hard ceiling 150; 10 customers at 5 mailboxes = $9.90 each with warming
Growth plan$189/mo200 mailboxes; hard ceiling 600; 40 customers at 5 mailboxes = $7.73 each with warming
Scale plan$499/mo600 mailboxes; hard ceiling 1,800; 120 customers at 5 mailboxes = $7.16 each with warming
Extra mailbox$1/moUp to 3x the included count
Warming$0.60 per inbox per month
Additional 5-mailbox customer past the plan$8.00/mo5 x ($1 + $0.60)
DomainsAbout $1 to $10 once eachPrepaid balance; exact price from POST /domains/search
Pre-warmed inbox lease$3/mo each90-day minimum, outside the plan allowance
Cold sends per inbox15/day recommended, 100 hard cap
Client workspaces, per-customer logins, white-labelNot available

Checked against the product and the API on . Plans and limits change, so confirm in the docs before you build on them.

This is not for you if

  • You need a white-label dashboard your customers log in to. EmailPal has no white-label, and reselling the dashboard or using the EmailPal brand as your own needs a written agreement.
  • You need isolated workspaces per customer, with separate keys, blocklists, verification allowances or API budgets. There is one account, and tags only label what belongs to whom.
  • You want to give customers their own EmailPal API keys. Keys act with the full authority of your account and must not be shared between organisations.
  • You cannot take responsibility for your end users’ sending. Under the Terms their actions are your acts, and enforcement against one customer’s sending can limit the whole account.
  • You need per-customer cost reporting from EmailPal. The invoice is one account-level bill, so metering is yours to build.
  • Your customers must connect their own Google Workspace or Microsoft 365 mailboxes by OAuth. EmailPal provisions its own SMTP and IMAP mailboxes.
  • You need more than 1,000 API requests an hour without a conversation. That is the plan default.
FAQ

Common questions

Can I offer cold email to my own SaaS customers on top of EmailPal?

Yes, through the API. Section 9 of the Terms lets you embed mailbox and domain provisioning in your own product and supply it to your own end customers through the REST API or MCP server, under your own account and your own name. You are responsible for their compliance with the acceptable use policy. White-labelling, reselling dashboard access or using the EmailPal brand needs written agreement.

How do I keep one customer’s mailboxes separate from another’s?

Inside EmailPal, only by tags: send tags: ["tenant:<id>"] on POST /domains and POST /mailboxes and filter with ?tag=. Tags are not an access boundary, because every key sees the whole account. The real separation is in your backend, which must map each customer to their domain and mailbox ids and check ownership before every call.

How much does the infrastructure cost per customer?

At five mailboxes per customer with warming, about $9.90 on Starter (10 customers), $7.73 on Growth (40) and $7.16 on Scale (120), and $8.00 for each customer added beyond the plan. Domains add a one-time cost of about $1 to $10 each. These are arithmetic on list prices and exclude payment fees, hosting, support and unfilled capacity.

How many customers can one account hold?

The mailbox hard ceiling is three times the included count: 150, 600 or 1,800 mailboxes on Starter, Growth and Scale, which is 30, 120 or 360 customers at five mailboxes each. Past the ceiling POST /mailboxes returns 402 plan_limit and growth needs a conversation first. The multiple can be changed per account by EmailPal, but it is not something to plan on.

Should I poll jobs or use webhooks?

Use webhooks. The account has 1,000 API requests an hour and failed requests count, so polling many jobs every few seconds exhausts it. Subscribe to domain.ready, mailbox.provisioned, mailbox.failed, domain.failed and job.succeeded, verify the EmailPal-Signature header, and dedupe on the event id because delivery is at-least-once.

What happens if one of my customers sends to a bad list?

Their campaign pauses at 5% hard bounces after 200 sends and the mailbox pauses at 5% rejected mail. Beyond that, the platform can throttle or pause any mailbox, domain, IP or the whole account, and an account under throttled or no_new_mailboxes enforcement cannot add domains or mailboxes. Because their actions are treated as yours, screen sign-ups and lists before they send.

Can I give customers their own inbox view?

Not from EmailPal directly. The Unibox is one account-wide inbox without per-customer logins. You build the view yourself on GET /inbox by mailbox_id and the message.received webhook, and send replies with POST /inbox/send, checking in your own backend that the mailbox belongs to the requesting customer.

Build it on infrastructure that holds up

Domains, mailboxes, warming and verification over one REST API and MCP server, with the sequencer, Unibox and CRM on every plan.

No card to start — cancel any time.