Mail guides
01 · Foundations

The Mail mental model.

Workspace, domain, mailbox, template, suppression list — the five primitives. Five minutes of reading saves an hour of misconfigured DNS.


1

Workspace = boundary

Everything in Mail is scoped to your workspace. Your domains, your templates, your suppression list, your API keys, your bounce events. Two workspaces share no data. Activate Mail on the workspace (Console → Services → Mail → Enable) before anything works — workspaces without Mail enabled return 403 SERVICE_NOT_ENABLED.


2

Domain

A domain is what your customer sees in their From: header — e.g. acme.com sends as noreply@acme.com. Each domain holds its own DKIM keypair, gets independently verified via DNS, and tracks its own deliverability stats. Customers running staging + prod usually verify both acme.com and staging.acme.com.

Domain status moves through:

  • pending — created, DNS records not yet verified. Cannot send.
  • active — DNS verified, can send.
  • failed — DNS check rejected (wrong record, wrong key). Re-verify after fixing.
  • suspended — operator-disabled.

3

Mailbox

A mailbox is a specific localpart@domain you send from. noreply@acme.com, support@acme.com, billing@acme.com are three mailboxes on one domain. You don't need to enumerate every mailbox you ever want to use; create the ones you send from and Mail keeps the per-mailbox bounce + reputation metadata in the dashboard.

For inbound — yes, mailboxes can receive too. The mx-receiver service catches inbound mail on your domain, parses it (subject, body, attachments), and stores it under the destination mailbox. You can list, fetch, mark-as-read, delete. Useful for support inboxes, reply-tracking, and email-to-API integrations.


4

Template

A template is your message body, authored in Handlebars. One template = one logical message (welcome email, order confirmation, password reset). You version templates by editing in place; previous versions aren't retained on this service — keep a copy in your repo if that matters.

Templates have:

  • name — unique within the workspace.welcome, password-reset,order-confirmation. Used in the send call.
  • subject + body — both Handlebars-templated.
  • tags — optional, used by the console for filtering / organisation.

5

Suppression list

A workspace-scoped block list. Once a recipient is on it, Mail refuses to send to that address regardless of the template or sender. Entries land on the list automatically when:

  • The recipient bounces (hard bounce → permanent suppression).
  • The recipient marks the email as spam.
  • You add the address manually (e.g. unsubscribe form).

Soft bounces (mailbox full, temporary failure) don't suppress. After 5 soft bounces in 7 days, the address gets promoted to hard-suppressed.

The suppression list is a hard wall. Attempting to send to a suppressed address returns 422 RECIPIENT_SUPPRESSED immediately — no message row is created, no bounce metric increments. The address stays suppressed until you (or the recipient via your unsubscribe form) explicitly removes it.


6

Sender model

When you call send, three things have to align:

  • The mailbox you're sending from must exist + be active.
  • Its domain must be verified.
  • The recipient must not be on the suppression list.

Failing any of the three returns a 412 / 422 immediately. No message row is created; no DKIM signing happens. The send call is the right place to find configuration mistakes — surface 4xx body messages to your operator UI so people see “Domain not verified” without trawling logs.


7

API auth

All Mail endpoints are workspace-scoped — paths like/workspaces/:workspaceId/.... Auth is the same shared ProductCraft cookie that the console uses, OR a workspace PAK (pcft_live_*). For programmatic access from your backend, mint a PAK with the mail.* scopes you need (the API reference calls out each route's gate).

curl with a PAK
curl https://api.mail.productcraft.co/v1/workspaces/<workspaceId>/domains \
  -H 'authorization: Bearer pcft_live_...'