# Email deliverability and sending health

The bounce and complaint limits that pause a campaign, the never-email lists, bounce intake for every sender, and why sign-in mail keeps flowing.

Deliverability is whether your email reaches the inbox at all. Gmail, Yahoo and
the other mailbox providers judge you mostly on two numbers. One is how much of
your mail bounces (comes back undelivered). The other is how much is marked as
spam, which is called a complaint.

By the time Gmail or Yahoo acts on those numbers, every message from your
domain suffers. So BuildBase watches both as mail goes out, and stops marketing
mail first. The mail people are waiting on to sign in is kept out of every one
of these stops.

```bash
# Is marketing mail blocked, and how much transactional mail went out today?
curl https://api.console.buildbase.app/api/emails/health \
  -H "Authorization: $BUILDBASE_TOKEN"
```

```json
{
  "marketingBlocked": false,
  "blockedAt": null,
  "reason": null,
  "transactionalToday": 182,
  "authCodesToday": 40,
  "authCodeDayMultiplier": 10
}
```

> **Note:**
  BuildBase needs to hear about your bounces. Mailgun and Amazon SES tell it
  with a signed webhook (a message from the provider that BuildBase can check
  really came from it). For SMTP and Gmail senders, BuildBase reads them from
  the sender's own mailbox instead (see [Bounce intake](#bounce-intake)). Set
  this up under **Messaging** > **Sender accounts**.

## Bounce and complaint limits

There are two levels. Each needs both a rate and a minimum count, so the first
bounce of a 40-person send is not "2.5% bounced, stop everything".

| Level     | Bounce rate          | Complaint rate         | What happens                                        |
| --------- | -------------------- | ---------------------- | --------------------------------------------------- |
| **Pause** | over 2%, at least 5  | over 0.08%, at least 2 | This campaign pauses, and its console page says why |
| **Block** | over 5%, at least 10 | over 0.3%, at least 3  | All of the organization's marketing mail stops      |

The pause level is checked for each campaign, before every batch. The block
level covers all of the organization's marketing mail over the last seven days,
and is checked each time a bounce or complaint comes in. Complaints are checked
first, because they weigh more.

A paused campaign resumes when a person resumes it, after looking at its list.
An owner or admin lifts a block with **Lift block** on **Messaging** >
**Campaigns**, or with `POST /api/emails/health/unblock`. Lifting
it resumes nothing: every campaign the block paused stays paused until someone
resumes it.

Sign-in and transactional mail are never blocked here. A sign-in code is not
the mail that drew the complaints, and refusing it would lock people out.

## Bounce intake

How bounces get back to BuildBase depends on the sender:

| Sender         | How bounces come back                                                                   |
| -------------- | --------------------------------------------------------------------------------------- |
| Mailgun        | A webhook signed with your Mailgun signing key, at `/api/webhooks/email/:orgId/mailgun` |
| Amazon SES     | Your SNS topic (Amazon's notification service), at `/api/webhooks/email/:orgId/ses`     |
| SMTP and Gmail | The **bounce mailbox**, read every ten minutes                                          |

An SMTP server or Gmail doesn't call a webhook. It mails a bounce notice back to
the address the message came from. To catch those, turn on bounce tracking in
the sender's edit dialog:

- For an SMTP sender, give the IMAP login of the mailbox that receives its
  bounces (port 993 by default). IMAP is how mail apps read a mailbox.
- For a Gmail sender, let it read its own inbox.

BuildBase is careful in that mailbox. It reads only recent mail from a mail
system (mailer-daemon, postmaster). It believes only a standard delivery status
report that names a message it sent for your organization. It marks a matched
notice read and touches nothing else. A notice without the message's tracking
code is ignored, so nobody can forge a bounce to get an address suppressed.

A hard bounce (an address that does not exist) or a complaint is marked on the
message's history row. The contact is flagged so no campaign mails it again,
and any workflow listening for that event starts.

## Your never-email list

Every organization has its own suppression list: addresses BuildBase will not
send to for you. It catches two things a contact flag cannot: an address that
is not a contact (a user, or a one-off send), and a spam trap. A spam trap is
an address a mailbox provider or blocklist runs only to catch senders with bad
lists. Once listed, it stays off every list for good, even if it is imported
again.

The [email API](/server-sdk/sending-email) refuses a suppressed address up front
with `RECIPIENT_SUPPRESSED`, instead of skipping it silently.

## The shared never-email list

A dead mailbox or a spam trap hurts whichever sender mails it next. So when one
organization's provider reports one, the shared list lets every other
organization on the same BuildBase server skip it too. The rules:

- Only hard bounces and spam traps go on it. Unsubscribes and complaints never
  do.
- Only what a provider reported counts (a signed webhook or a bounce mailbox).
  An address someone marked by hand never does.
- A bounce or a spam trap counts only once two different paying billing
  accounts have reported it, so one customer cannot list an address, even with
  two organizations.
- Each address is stored as a keyed hash (a one-way scramble of it) under the
  shared list's own key, which never matches a row of your own list.
- A skip gives one neutral reason, `Address not deliverable`, the same for a
  bounce and a trap.

An undeliverable entry lasts 180 days and a trap 365. A skip it causes counts as
a bounce in your own sending health. So testing addresses against the list
costs you what mailing them would.

> **Note:**
  The shared list is built, and off by default. BuildBase turns it on for a
  server once its data processing terms cover it. Until then, only your own list
  and your contacts' flags apply.

## DMARC

DMARC is a DNS record that tells mailbox providers what to do with mail that
claims to come from your domain but fails their checks. Gmail and Yahoo require
one from bulk senders, even `p=none`, a policy that blocks nothing.

Verifying a [sending domain](/email/sending-domains) looks for it at
`_dmarc.<domain>`. When a subdomain has none, it walks up to the main domain.
The result shows as one more DNS row: the policy found, or the record to
publish, `v=DMARC1; p=none;`. Connect the domain to
[Gmail Postmaster](/email/gmail-postmaster) to see Gmail's own SPF, DKIM and
DMARC results for it. (SPF and DKIM are the two other DNS checks that prove
mail really came from you.)

## Sign-in mail is never held up

People are sitting on the sign-in screen waiting for these, so they are handled
differently from everything else.

- **Untracked.** Sign-in links and codes, verification, resets, changes of
  address, security notices and invitations go out with no open-tracking image
  and no rewritten links. A live sign-in token never passes through a redirect
  or a log.
- **Never behind a campaign.** Sending is paced per organization and per
  sender, so one large campaign cannot take over the mail queue everyone
  shares. A message over its pace is delayed a few seconds, never dropped.
  Sign-in mail skips the pacing.
- **Its own day.** Codes a person is waiting on get their own daily allowance,
  ten times your plan's transactional limit. A per-recipient hourly cap stops
  anyone using your sign-up form to flood one inbox.

## When BuildBase suspends sending

Platform staff can suspend an organization's sending, for example while abuse
is investigated. A suspension has two levels:

| Level  | Stops                                                                                    | Keeps flowing                                                                       |
| ------ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `bulk` | Campaigns, workflow and custom mail, the email API, every web push, sender and SMS tests | Sign-in codes, magic links, resets, verification and invitations, and sign-in texts |
| `all`  | Everything, sign-in mail included                                                        | Nothing                                                                             |

A console action answers `403` with `SENDING_SUSPENDED`, and so does the email
API. A campaign message caught by a suspension is held, never skipped, and goes
out once sending is restored. A queued push or a workflow step is skipped with
the reason and never retried.

## Next Steps

- [Sending campaigns](/email/sending-campaigns) - the pre-send check, the first-send sample and pausing.
- [Gmail Postmaster](/email/gmail-postmaster) - Gmail's own spam rate for your domain.
- [Importing contacts](/email/importing-contacts) - the list hygiene checks on the way in.
