> ## Documentation Index
> Fetch the complete documentation index at: https://gtmapis.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Email Outcome Calibration

> Import delivery outcomes and read privacy-safe validation accuracy reports

# Email Outcome Calibration

Build an owned accuracy loop by attaching real delivery and bounce outcomes to
earlier email-validation decisions. Outcome operations are append-only and
charge zero credits.

Use separate API keys in production:

* `email:outcomes:write` imports evidence but cannot read reports.
* `email:outcomes:read` reads aggregate reports but cannot upload addresses.

## Automate EmailBison outcomes

Create a revocable, write-only connector URL with an API key carrying
`email:outcomes:write`:

```bash cURL theme={null}
curl -X POST https://api.gtmapis.com/v1/outcomes/email/connectors/emailbison \
  -H "X-API-Key: $GTM_EMAIL_OUTCOMES_WRITE_KEY"
```

The response returns `webhook_url` once. Add that URL in EmailBison and enable
only `email_sent`, `email_opened`, `email_bounced`, `lead_replied`,
`lead_interested`, and `lead_unsubscribed`. Creating another connector rotates
the credential and immediately invalidates the previous URL.

`email_sent` is mandatory because it supplies the denominator for prospective
bounce-rate measurement. If other events arrive before any sent event, the
connector reports `denominator_status=sent_event_missing` and all accuracy
gates remain fail-closed.

GTM APIs stores only the connector-token hash. The webhook accepts only a
bounded JSON body, deduplicates retries by EmailBison's stable scheduled-email
ID, and never stores campaign message bodies or arbitrary EmailBison metadata.
Known DSN fields are used transiently for bounce classification. Replies become
confirmed delivery evidence; opens and sent-with-no-bounce observations remain
separate, weaker signals.

```bash cURL theme={null}
# Check status; the secret URL cannot be recovered.
curl https://api.gtmapis.com/v1/outcomes/email/connectors/emailbison \
  -H "X-API-Key: $GTM_EMAIL_OUTCOMES_WRITE_KEY"

# Revoke immediately if the URL is exposed.
curl -X DELETE https://api.gtmapis.com/v1/outcomes/email/connectors/emailbison \
  -H "X-API-Key: $GTM_EMAIL_OUTCOMES_WRITE_KEY"
```

Connector errors include stable codes. `connector_unavailable` means the
account cannot use this connector. Retry `connector_status_failed`,
`connector_create_failed`, or `connector_revoke_failed` with bounded backoff.
After `connector_revoke_failed`, treat the existing webhook credential as
active until a later revoke request succeeds.

## Import outcomes

```text theme={null}
POST https://api.gtmapis.com/v1/outcomes/email/import
```

Each request accepts 1–10,000 outcomes and a body up to 5 MB. `source` plus `external_id` is the
idempotency key. Include the provider's enhanced SMTP code and diagnostic text
when available; specific DSN evidence takes precedence over a coarse label such
as `bounce` or `rejected`.

```bash cURL theme={null}
curl -X POST https://api.gtmapis.com/v1/outcomes/email/import \
  -H "X-API-Key: $GTM_EMAIL_OUTCOMES_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "outcomes": [{
      "email": "person@company.com",
      "outcome": "bounce",
      "source": "webhook",
      "external_id": "provider-event-123",
      "occurred_at": "2026-07-31T12:00:00Z",
      "action": "failed",
      "smtp_enhanced_code": "5.1.1",
      "diagnostic_code": "550 5.1.1 Recipient not found"
    }]
  }'
```

You can also post an EmailBison-style diagnostics export directly. The importer
recognizes `email` or `lead_email`, `bounce_type`, `status`, `smtp_code`,
`action`, `enhanced_status`, `bounce_reason`, `diagnostic`, `dsn_message`, `dsn_subject`,
`bounced_at`, `arrival_date`, `last_attempt_date`, `will_retry_until`, `original_recipient`,
`final_recipient`, `remote_mta`, `reporting_mta`, `dsn_gateway`, `received_from_mta`, and `emailbison_lead_id`
columns. Raw diagnostic text is used for classification but is not persisted as
outcome metadata.
RFC-native exports can use `Action` and `Status` directly. The importer keeps
the transport-independent `Status` code as classification evidence instead of
replacing it with the coarse event outcome. `Action` accepts only `failed`,
`delayed`, `delivered`, `relayed`, or `expanded`, without regard to case. A
different non-empty value rejects that row as `invalid_dsn_semantics` and
cannot create a suppression. Balanced RFC comments are ignored before the
action is selected. Comment text cannot supply or override the action, and a
malformed comment rejects the row.
`Action` and `Status` are not redundant. A `failed` report can keep a 4.x
temporary-failure Status after the MTA abandons retries. The importer accepts
that standards-defined pair as `bounced_soft` and does not create a permanent
recipient suppression.
When a recognized `Action` is supplied, a non-empty `Status` must use the RFC
3464 status-code form, such as `5.1.1`. The second and third fields cannot have
leading zeroes, and an RFC comment can follow the code. Comment text is ignored
for classification and cannot add another status code or diagnostic. Malformed
structured Status evidence rejects that row as `invalid_dsn_semantics` and
cannot create a suppression. Without an RFC `Action`, generic vendor `status`
text keeps its existing import behavior.
RFC `Original-Recipient` and `Final-Recipient` values use
`rfc822; person@example.com` form. When `Original-Recipient` is supplied, it
must match `email` and can safely correlate a different final address after
forwarding. Without it, `Final-Recipient` must match `email`. Malformed,
foreign-address, and conflicting recipient evidence rejects the row before it
can affect suppression or accuracy reports.
RFC comments are removed before the address type and mailbox are checked.
Comment text cannot supply either value, and malformed comments reject the row.
RFC `Remote-MTA` uses `dns; mx.example.com` form when the remote system has a
DNS name. A valid DNS hostname is bounded provider evidence for the system that
issued the diagnostic, and it takes precedence over route aliases and
diagnostic prose. Foreign MTA types, malformed hosts, IP literals, and
provider lookalikes remain non-attributing evidence and cannot activate a
provider-specific rule.
RFC comments are removed before the MTA type and hostname are checked. Comment
text cannot supply either value, and malformed comments fail closed.
RFC `Reporting-MTA` uses the same typed form and is required in a complete DSN.
When `Remote-MTA` is absent, a valid DNS Reporting-MTA identifies the system
that issued the diagnostic and takes precedence over route aliases and prose.
When `Remote-MTA` is supplied, it always controls issuer selection. An invalid
or foreign Remote-MTA blocks Reporting-MTA fallback instead of attributing the
remote diagnostic to the reporting gateway.
RFC `DSN-Gateway` identifies the gateway that translated a foreign delivery
notification. It is not the diagnostic issuer. `Remote-MTA` or `Reporting-MTA`
still controls attribution when present. Without either issuer field, supplied
DSN-Gateway evidence blocks provider inference from route aliases and prose.
RFC `Received-From-MTA` identifies the upstream MTA that handed the message to
the reporting MTA. It is not the diagnostic issuer. `Remote-MTA` or
`Reporting-MTA` still controls attribution when present. Without either issuer
field, supplied Received-From-MTA evidence also blocks route-alias and prose
provider inference.
When `occurred_at` is absent, RFC `Last-Attempt-Date` supplies the delivery
attempt time. It must use RFC 822 date-time syntax with a numeric `+HHMM` or
`-HHMM` timezone. Invalid or future-dated values reject that row instead of
falling back to import time. An explicit valid `occurred_at` takes precedence.
`occurred_at` uses RFC 3339 syntax. Lowercase `t` and `z` are accepted and
normalized to uppercase UTC. `Z`, `+00:00`, and `-00:00` all identify a UTC
instant; `-00:00` additionally reports that local-zone provenance is unknown.
Fractions through the sixth fractional position are preserved and compared at
microsecond precision. Non-zero precision beyond that position is rejected as
`invalid_timestamp` before PostgreSQL can round it; supply the source system's
canonical timestamp at microsecond precision or lower.
Leap-second notation such as `23:59:60Z` is rejected as `invalid_timestamp`
because the canonical UTC chronology cannot represent the extra second
without loss. Supply the source system's canonical POSIX timestamp instead.
RFC `Arrival-Date` uses the same numeric-timezone syntax and records when the
message reached the Reporting MTA. It never replaces `occurred_at` or
`Last-Attempt-Date` as the outcome time. Malformed or future values reject the
row, as does an Arrival-Date later than a supplied valid Last-Attempt-Date.
RFC `Will-Retry-Until` uses the same syntax and is accepted only with
`Action: delayed`. It records the retry horizon, so a future value is valid and
never becomes `occurred_at`. Malformed values, use with any other action, and a
deadline before Arrival-Date or Last-Attempt-Date reject the row.
For all three RFC date fields, balanced comments are removed before parsing.
Comment text cannot supply or replace the timestamp, and malformed comments
reject the row through the same date-field error.
The weekday is optional. When supplied, it must match the stated local calendar
date before timezone conversion. A mismatch rejects the row through the same
date-field error and cannot affect suppression freshness.
Both `+0000` and `-0000` identify Universal Time. The `-0000` form additionally
reports that local-zone provenance is unknown; it is accepted and normalized to
the same canonical UTC instant.

```bash cURL theme={null}
curl --data-binary @bounced_emails_diagnostics.csv \
  -H "X-API-Key: $GTM_EMAIL_OUTCOMES_WRITE_KEY" \
  -H "Content-Type: text/csv" \
  https://api.gtmapis.com/v1/outcomes/email/import
```

```json theme={null}
{
  "received": 1,
  "accepted": 1,
  "skipped_invalid": 0,
  "duplicates": 0,
  "suppressed": 1,
  "complaints_suppressed": 0,
  "unsubscribed_suppressed": 0,
  "stale_suppressions_skipped": 0,
  "suppression_cache_invalidated": 1,
  "errors": [],
  "credits_charged": 0,
  "imported_at": "2026-07-31T12:01:00.000Z"
}
```

Invalid rows include a zero-based `index`, stable machine-readable `code`, and
human-readable `reason`; the response does not repeat their email addresses.
Branch on `code`, do not parse `reason`, and preserve unknown non-empty future
codes.

| Row code                          | Recommended action                                                     |
| --------------------------------- | ---------------------------------------------------------------------- |
| `invalid_email`                   | Supply a valid email address.                                          |
| `missing_outcome`                 | Supply a supported outcome.                                            |
| `invalid_source`                  | Supply a supported source.                                             |
| `invalid_timestamp`               | Correct the supplied event or DSN timestamp.                           |
| `missing_event_timestamp`         | Add `occurred_at` or `last_attempt_date` for webhook sources.          |
| `invalid_dsn_semantics`           | Correct conflicting RFC DSN fields or ordering.                        |
| `invalid_external_id`             | Use a bounded printable external ID.                                   |
| `missing_external_id`             | Add an immutable provider event ID.                                    |
| `invalid_metadata`                | Supply a JSON object with valid recognized fields.                     |
| `invalid_recipient_identity`      | Make typed DSN recipient fields agree with `email`.                    |
| `invalid_classification_evidence` | Correct malformed SMTP or DSN evidence.                                |
| `event_identity_conflict`         | Give each different event a different source and external ID identity. |
| `invalid_row`                     | Review the human reason; this is the forward-compatible fallback.      |

Import request errors use `invalid_json`, `invalid_csv`, `missing_outcomes`,
`too_many_outcomes`, or `payload_too_large`. Retry `outcome_import_failed` with
the same provider event identities. `outcome_suppression_failed` means the
outcome import did not finish its fail-closed suppression step; retry the same
import rather than creating different event identities.

### Evidence semantics

| Evidence                                                                         | Calibration outcome                                              |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Confirmed delivery                                                               | `delivered`                                                      |
| Recipient `reply`, `replied`, `positive_reply`, or `negative_reply`              | `delivered`                                                      |
| Recipient-not-found DSN such as `5.1.1` or `5.1.10`                              | `bounced_hard`                                                   |
| RFC DSN `action=delayed`, including with a 5.x status                            | `bounced_soft`                                                   |
| RFC DSN `action=delivered`, `relayed`, or `expanded` in a bounce-like row        | `unknown`; no suppression                                        |
| RFC DSN `action=failed` with `status=5.1.1`                                      | `bounced_hard`                                                   |
| Invalid non-empty RFC DSN `action`                                               | Row rejected as `invalid_dsn_semantics`; no suppression          |
| Invalid RFC DSN `status` with a recognized `action`                              | Row rejected as `invalid_dsn_semantics`; no suppression          |
| Temporary `4.x` response, mailbox full, or routing deferral                      | `bounced_soft`                                                   |
| Status-free, unqualified `DNS error` or `DNS failure`                            | `bounced_soft`; no suppression                                   |
| `5.7.x`, sender reputation, authentication, or policy rejection                  | `blocked_policy`                                                 |
| Generic `bounce` or `rejected` without specific evidence                         | `unknown`                                                        |
| Coarse `hard`, `hard_bounce`, `invalid`, or `undeliverable` without DSN evidence | `unknown`                                                        |
| Sender-side `sent`, `accepted`, `success`, `ok`, or `no_bounce`                  | `unknown`                                                        |
| Tracking-only `opened` or `clicked`                                              | `unknown` outcome; open tracked separately as a non-claim signal |

Only `delivered` and confirmed `bounced_hard` are decision-grade mailbox labels.
For RFC DSNs, include the per-recipient `action` field. `failed` leaves the
specific status and diagnostic in control. `delayed` means the MTA will keep
trying, while `delivered`, `relayed`, and `expanded` do not report a bounce
failure and cannot create a recipient suppression.
RFC continuation lines are accepted only as `CRLF` followed by a space or tab.
Each legal fold becomes one space before parsing. Bare LF, bare CR, and
unindented continuation lines reject structured evidence instead of being
treated as field contents.
For a typed RFC `Diagnostic-Code`, comments in parentheses are human-readable
context only. The classifier removes them before it reads the diagnostic type
or SMTP reply. Comment text cannot create SMTP evidence, and a foreign
diagnostic type remains opaque. Untyped vendor diagnostics keep the legacy
compatibility parser.
Any other supplied non-empty action value is invalid structured evidence. The
importer rejects that row instead of ignoring the value or applying its status
and diagnostic.
Use `delivered` only for a provider's final delivery event. A successful API
request, queued message, SMTP handoff, or absence of a bounce is not delivery
proof. `smartlead` and `webhook` rows must include an immutable `external_id`
plus either an RFC 3339 `occurred_at` or RFC `Last-Attempt-Date`. This prevents
provider retries from changing event meaning and prevents old evidence from
becoming fresh at import time. `csv` and `manual` rows can omit these fields;
their missing timestamp uses import time only when `Last-Attempt-Date` is also
absent. A supplied invalid timestamp is always rejected instead of being
replaced with the import time.
A recipient reply is accepted as confirmed delivery evidence. Opens and clicks
are not: privacy proxies and security scanners can generate them without proving
recipient mailbox engagement.
Confirmed permanent recipient bounces are added to that tenant's mailbox
suppression list only while the evidence is fresh: mailbox-not-found evidence
is reused for 90 days, disabled/inactive mailbox evidence for 14 days, and
domain/DNS failure evidence for 24 hours. Older rows remain in calibration but
increment `stale_suppressions_skipped` and require a fresh validation. Expiry
never marks an address valid or recommends sending. Explicit complaints and
unsubscribes are stored independently as durable contact
preferences: they return `unknown` plus `send_recommendation=do_not_send`
instead of falsely labeling the mailbox invalid. Policy, reputation, and
temporary delivery failures are not suppressed.
An unqualified `DNS error` or `DNS failure` without structured status evidence
is retryable because it does not distinguish a missing domain from a temporary
resolver outage. Only explicit Null MX, no-mail-route, no-MX, or recipient-domain
nonexistence evidence can create a domain/DNS suppression.

### Reusing confirmed deliveries

A recent `delivered` outcome can improve future coverage for the same tenant
without another SMTP probe. The reuse gate requires all of the following:

* the delivery occurred within the previous 30 days;
* `source` is `csv`, `smartlead`, or `webhook` and `external_id` is present;
* this tenant has an immutable, current-policy validation decision from before
  the delivery;
* the latest decision-grade outcome is still `delivered`; and
* no tenant hard-bounce suppression exists for the address.

Evidence-backed responses return `validation_source=recent_delivery_evidence`
and charge zero credits. Delivery evidence is never shared between tenants or
written into the global validation cache. A manual outcome is retained for
calibration but cannot create a positive validation result.

### Platform recipient bounce consensus

GTM APIs can conservatively downgrade an otherwise SMTP-accepted recipient when
provider-observed message outcomes show recent hard bounces from at least three
independent tenants. Each tenant contributes at most one latest vote, every send
must follow an immutable current-policy SMTP-accepted decision, and any confirmed
reply within the 30-day evidence window vetoes the downgrade.

Consensus responses retain `smtp_valid=true` as raw protocol evidence, return
`result=risky`, `validation_source=platform_recipient_bounce_consensus`,
`send_recommendation=do_not_send`, and charge zero credits. The function is
service-role only and returns no tenant, workspace, message, or event detail.
It never labels the recipient permanently invalid.

## Read the calibration report

```text theme={null}
GET https://api.gtmapis.com/v1/outcomes/email/report
```

The default window is the previous 90 days. Supply optional strict RFC 3339
`start` and `end` values with `Z` or a numeric UTC offset for a window of at
most 366 days. Date-only, zone-less, locale-style, and padded timestamps return
`invalid_report_timestamp`.

Repair `invalid_report_timestamp`, `invalid_report_range`, or
`report_range_too_large` before retrying. Retry `outcome_report_failed` with
bounded backoff.

```bash cURL theme={null}
curl 'https://api.gtmapis.com/v1/outcomes/email/report?start=2026-07-01T00%3A00%3A00Z&end=2026-08-01T00%3A00%3A00Z' \
  -H "X-API-Key: $GTM_EMAIL_OUTCOMES_READ_KEY"
```

The response contains the original `report`, a `send_observations` report, and
a `gateway_shadow` report.
The latter measures the latest unique recipient sent through EmailBison and
separates:

* confirmed delivery from replies/interested events;
* open signals, which may come from proxies or scanners;
* hard, soft, policy, and unknown bounces;
* sends matured for seven days without any bounce; and
* unresolved recent sends that have not yet reached the maturity window.

`gateway_shadow` evaluates enterprise-gateway inference separately for
Proofpoint, Mimecast, Barracuda, Cisco, Sophos, Trend Micro, Fortinet, Broadcom,
Hornetsecurity, and an `other_gateway` fallback. Each gateway/action pair remains
in shadow until it has at least 300 decision-grade unique-recipient outcomes,
an observed hard-bounce rate no greater than 1%, and a Wilson 95% upper bound no
greater than 1%. A passing family never promotes a different gateway family or
a different inferred action.

The response also includes `evidence_readiness`. Treat
`evidence_readiness.market_claim_ready` as the only public market-claim signal.
It becomes true only when the statistical and provider-breadth claim gates pass
and the prospective ledger has at least 300 unique sends, at least 95%
validation linkage, and at least 90% outcome resolution. The same denominator
gate is also evaluated independently in
`send_observations.provider_evidence_gates`; another provider's sends cannot
satisfy the Microsoft gate.

Before promoting a Microsoft SMTP route, require
`evidence_readiness.microsoft_route_outcome_ready=true`. It combines the
Microsoft matched-outcome operational gate with the Microsoft-only prospective
send gate. It does not replace the separate route-bound controlled canary.

Both nested reports expose `decision_version`. Only predictions created by that
exact policy version enter calibration. Sends tied only to a legacy policy stay
in the prospective denominator as unmatched, so every decision-changing release
must establish fresh linkage and outcome evidence.

Claim-grade predictions are read from an append-only ledger with stable decision
IDs and timestamps. The operational history table can still be upserted during
CSV retries, but those mutations cannot rewrite or backdate accuracy evidence.
Older mutable rows are intentionally not backfilled.

Matured no-bounce rows contribute only to operational bounce-risk resolution.
They never count as confirmed delivery, reusable address evidence, or a
claim-grade positive control. A late bounce automatically replaces the inferred
no-bounce state because maturity is calculated from the current observation
ledger rather than persisted as a delivery outcome.

Both reports contain aggregate counts and rates only. Raw email addresses,
domains, and MX hosts remain inside the tenant-scoped database join and are
never returned. Outcomes use bounded provider segments: Microsoft consumer,
authoritative Microsoft 365, security gateway, Google, Yahoo, Zoho, Amazon,
Apple, Fastmail, Proton, and `other`.

Promotion gates include:

* one latest decision-grade label per unique address, so repeated sends to the
  same recipient cannot inflate sample size or statistical confidence;
* observed hard-bounce rate for `valid` and `valid_role_based` decisions;
* the Wilson 95% upper confidence bound;
* Wilson bounds for delivery recall and false-invalid risk;
* a minimum of 300 decision-grade outcomes per Microsoft segment;
* at least 50% send-decision coverage across decision-grade segment outcomes;
* at least 90% delivery send recall, so an overly cautious policy cannot win by
  hiding deliverable mail behind `risky`, `unknown`, or `invalid`;
* no more than 1% delivered-after-`invalid` outcomes;
* separate operational and statistically supported sub-1% claim states;
* an overall gate plus gates for each bounded provider segment;
* provider-breadth readiness requiring at least four named provider families,
  each with at least 20 decision-grade outcomes; and
* delivered-after-`invalid` and hard-bounce-after-send-recommended counters.

A passing observed rate does not automatically authorize a public accuracy
claim. Both promotion states require coverage, delivery-recall, and
false-invalid gates to pass; use
`sub_one_percent_claim_ready` for the stricter state that also requires the
hard-bounce and false-invalid upper bounds plus the delivery-recall lower bound
to meet their targets.

Use `report.readiness.operational_rollout_ready` as the fail-closed rollout
signal. It requires the overall operational gate, the combined Microsoft gate,
and provider breadth to pass. Use
`report.readiness.sub_one_percent_claim_ready` as the statistical claim result:
the overall and combined Microsoft confidence gates must pass, and at least
four provider families must independently be claim-grade. A large cohort from
only Microsoft cannot satisfy either breadth requirement. The strongest
first-party prospective signal is `evidence_readiness.market_claim_ready`,
which additionally requires representative prospective send coverage,
including a passing Microsoft-only prospective gate.

Despite its compatibility name, that field does not evaluate another
validator. Comparative market-leadership language also requires a verified
competitive benchmark v9 report for GTMAPIs and at least two named competitors,
with every loss and abstention published. It must pass its domain-diversity
input gate and publish both subject-level paired tests and equal-domain-weight
exact sign tests.
