# Email status

Source: https://help.ruuster.com/api/email-status

> Ruuster's verdict on each lead's email address (valid, invalid, blocked, soft bounce, complaint, unsubscribed, unknown), how to read it, how to get it by lookup or webhook, and how to keep it in Follow Up Boss, Sure Send or Sierra Interactive through their APIs.

Ruuster checks a lead's address before the first email it sends them, and records what happens
on every send after that: bounces, blocks and drops from the mail provider, unsubscribes and spam
reports. This guide explains the one verdict Ruuster makes from that history and the two ways to
get it: a lookup, and a webhook event when it changes.

- **Look it up:** [Get a lead's email status](https://help.ruuster.com/api/leads-email-status), by email, CRM id or lead id.
- **Be told when it changes:** the `lead.email_status_changed` [webhook event](https://help.ruuster.com/api/webhook-events).
- **Keep it in your CRM:** write it to custom fields (or tags) through your CRM's API on each event, so smart lists and action plans can use it. See [Keep it in your CRM](#keep-it-in-your-crm).

## The statuses

| `status` | Means | What to do |
|---|---|---|
| `valid` | Ruuster is delivering to this address | Nothing |
| `invalid` | The mailbox does not exist, is closed or disabled, is a throwaway address or a spam trap, or the address is on the bounce list | Call or text instead, or get a new address |
| `blocked` | The recipient's mail provider refused the message for policy, reputation or content reasons. The mailbox itself may be fine | Use another channel for now |
| `soft_bounce` | A temporary problem: the mailbox is full, or their server did not answer | The address may still be good. Keep it |
| `complaint` | The lead reported a Ruuster email as spam, so sends to them are dropped | Do not email them from Ruuster again |
| `unsubscribed` | The lead unsubscribed from Ruuster emails, or the CRM record carries an opt-out tag | Respect it. If they ask to receive emails again, email helpme@ruuster.com |
| `unknown` | Not verified yet, or the check could not decide | Normal for new leads; see below |

**Precedence** when more than one thing is true: delivery on is always `valid`; the lead's own
unsubscribe outranks a later bounce; a recorded failure outranks a CRM opt-out tag.

**Why new leads are `unknown`.** Ruuster verifies an address right before the first email it
sends, not when the lead arrives. A lead who has not been emailed yet has not been checked yet.
About six in ten of a brokerage's newest contacts are in this state; it resolves on the first send.

## What comes with the status

| Field | Description |
|---|---|
| `code` | What is behind the verdict: the SMTP code and whether it was a bounce or a block (`5.1.1 bounce`, `4.2.2 block`), the verification status (`inbox_full`, `disposable`), or the suppression reason (`unsubscribed address`, `spam reporting address`). `null` when valid |
| `reason` | One sentence, in plain words |
| `source` | `platform` (the delivery flags), `verification` (the pre-send check), `delivery` (a send), `lead` (they unsubscribed), `crm` (an opt-out tag) |
| `checkedEmail` | The address the verdict is about, when lead contact details are on. Compare it with the lead's current email to tell whether a changed address has been checked yet |
| `failedAt` | When the failure behind the verdict was recorded, if there is one |
| `since` | When the verdict last changed |
| `previousStatus` | The verdict before this one, when known |

## Look up one lead

```bash
curl -s "https://api.ruuster.com/v1/leads/email-status?email=sam.lee%40example.com" \
  -H "x-api-key: $RUUSTER_API_KEY" -H "x-email: $RUUSTER_EMAIL"
```

The answer lists every lead with that address (a person can be a registered user and a CRM
contact), each with `emailStatus`. The verdict is refreshed every 15 minutes; a lead the refresh
has not reached yet, or whose address changed since, is classified on the call and comes back
with `since` and `previousStatus` null. It is a lookup call: 20 per minute per key.

In an AI assistant the tool is `leads_email_status`: "what's the email status for sam.lee@example.com".

## Be told when it changes

Subscribe to `lead.email_status_changed`, or to `lead.*`, which includes it. It fires when the
verdict moves: a check or a send fails, the lead unsubscribes, delivery is turned back on, or a new
lead's address gets its first verdict. `data.emailStatus` carries the fields above plus
`previousStatus`; `data.lead` names the lead and its `crmExternalId`. A brokerage's first fill
publishes no events; only changes after that do.

```json
{ "kind": "lead.email_status_changed", "occurredAt": "2026-10-29T14:10:00Z",
  "data": { "lead": { "leadId": "user:c0ff3e21-8a4b-4c7d-9e15-2b6f8d3a1c04", "firstName": "Sam", "lastName": "Lee", "crmExternalId": "48213", "agentId": "7c1e9b42-3d85-4a6f-9e07-1b2c8d4f5a69" },
            "emailStatus": { "status": "invalid", "code": "5.1.1 bounce",
                             "reason": "The mailbox does not exist or no longer accepts mail.",
                             "source": "delivery", "failedAt": "2026-10-29T14:02:11Z",
                             "since": "2026-10-29T14:10:00Z", "previousStatus": "valid" } } }
```

See [Webhooks](https://help.ruuster.com/api/webhooks) for subscribing and
[Verifying deliveries](https://help.ruuster.com/api/webhook-verifying) for signatures.

## Keep it in your CRM

Ruuster posts a "Ruuster Email Validation" note on the CRM contact when it validates an address,
but notes cannot be filtered. With the webhook and your CRM's own API you can keep the verdict
where lists and action plans can use it: custom fields in Follow Up Boss and Sure Send, tags in
Sierra Interactive (its API has no custom fields). `data.lead.crmExternalId` is the person's id in
your CRM.

What to store, from each event:

| Store | From | Follow Up Boss | Sure Send | Sierra Interactive |
|---|---|---|---|---|
| Status | `data.emailStatus.status` | `customRuusterEmailStatus` (dropdown) | custom field, dropdown | tag `Ruuster Email: invalid` (one of seven) |
| Status code | `data.emailStatus.code` | `customRuusterEmailStatusCode` (text) | custom field, text | a note, if wanted |
| Validated email | `data.lead.email` (lead contact details on) | `customRuusterValidatedEmail` (text) | custom field, text | a note, if wanted |
| Validation date | `data.emailStatus.failedAt`, else `since`, as `YYYY-MM-DD` | `customRuusterValidationDate` (date) | custom field, date | a note, if wanted |

Answer each delivery with a 2xx within 10 seconds, then make the CRM call. Deliveries are at least
once, so de-duplicate on the event `id` if a repeat would matter. A `PUT` on a person in all three
CRMs leaves every field you don't send untouched.

**Follow Up Boss.** Create the four fields once: through `POST /v1/customFields` with an owner key
(`label`, `type` of `dropdown`, `text` or `date`, and `choices` for the dropdown), or under Admin,
then Custom Fields. The `name` in each field (`customRuusterEmailStatus` and so on) is the key you
write. Then on each event:

```bash
curl -s -X PUT -u "$FUB_API_KEY:" -H "Content-Type: application/json" \
  "https://api.followupboss.com/v1/people/$CRM_EXTERNAL_ID" \
  -d '{"customRuusterEmailStatus":"invalid","customRuusterEmailStatusCode":"5.1.1 bounce",
       "customRuusterValidatedEmail":"sam.lee@example.com","customRuusterValidationDate":"2026-10-29"}'
```

**Sure Send.** Create the four fields once through `POST /customFields` (or in Settings) and note
the keys it gives them; a custom-field key that doesn't exist is dropped silently, so create them
first. Then on each event, with the keys from your account:

```bash
curl -s -X PUT -H "Authorization: Bearer $SURESEND_API_KEY" -H "Content-Type: application/json" \
  "https://api.suresend.ai/api/partner/people/$CRM_EXTERNAL_ID" \
  -d '{"customFields":{"ruusterEmailStatus":"invalid","ruusterEmailStatusCode":"5.1.1 bounce",
       "ruusterValidatedEmail":"sam.lee@example.com","ruusterValidationDate":"2026-10-29"}}'
```

**Sierra Interactive.** The API has no custom fields, so use one tag per status. `tags` adds and
`removeTags` removes, so send the new tag and remove the other six in the same call; the code and
date can go in a note (`POST /leads/{id}/note`) if you want them on the record:

```bash
curl -s -X PUT -H "Sierra-ApiKey: $SIERRA_API_KEY" -H "Content-Type: application/json" \
  "https://api.sierrainteractivedev.com/leads/$CRM_EXTERNAL_ID" \
  -d '{"tags":["Ruuster Email: invalid"],
       "removeTags":["Ruuster Email: valid","Ruuster Email: blocked","Ruuster Email: soft_bounce",
                     "Ruuster Email: complaint","Ruuster Email: unsubscribed","Ruuster Email: unknown"]}'
```

**Fill in existing contacts.** The event covers changes from now on. For contacts you want filled
today, call [Get a lead's email status](https://help.ruuster.com/api/leads-email-status) with each
one's `crmExternalId` and make the same write. It is a lookup call, 20 per minute per key, so
start with the contacts you email.

Then, for example: a smart list where the status is `invalid` or `complaint` and a phone is set is
your "call instead" list; `soft_bounce` stays in email plans; `unsubscribed` stays out of them;
and a contact whose email no longer equals the validated email has a new address Ruuster has not
checked yet. Ruuster re-validates it on its own once it syncs, and the next event updates the
fields.

## Good to know

- **Ruuster never emails an invalid, complaint or unsubscribed address.** The status tells you what Ruuster already stopped doing.
- **Resubscribing a lead.** When a lead who unsubscribed asks to receive emails again, email support at helpme@ruuster.com with the lead's name and address and we turn delivery back on; the status returns to `valid` and the change fires an event.
- **A repeat of the same failure is not a change.** A second full-mailbox bounce leaves `since` alone and sends no event. A different code, a different status or a changed address does.
- **One verdict per address.** Two CRM contacts with the same email get the same status.
- **A fixed address.** When the email is corrected in your CRM, the new address syncs to Ruuster within minutes and is checked before the next send. Until then `checkedEmail` still shows the old one.
- **Contact details off.** The lookup and the event still work; `checkedEmail`, `email` and `phone` are left out.
