Set where a receiving domain's mail is delivered

Search Documentation

Search across all developer documentation

inbound

Set where a receiving domain's mail is delivered

PUT /v1/inbound/domains/{domain}/webhook
Auth required api.inbound.manage

Full replacement, not a patch. An omitted allow_patterns means "deliver every recipient at this domain", not "leave the list as it was". That is the field on this surface most likely to be got wrong, because the two readings differ by the whole allow-list.

hmac_secret is present in the response ONLY when this call minted it — the first webhook save on a domain. It survives a URL change, because correcting a typo in an endpoint is not a request to re-key it, and re-keying silently would break signature verification on the endpoint you just fixed. Store it when you receive it; if you lose it, rotate.

The URL must be https and its host must resolve to a public address. Both are checked here AND again at publish time, because what DNS answers changes. A refusal is 422 with the detail on webhook_url, and no refusal ever names an address.

Saving re-publishes the routing into your landing bucket in the background, so the change reaches your intake function within about a minute.

The payload your endpoint then receives, and how to verify its signature, is documented at https://developers.sendops.dev/api-reference/inbound-webhook.

Path parameters

domain string required

The receiving domain, by NAME (in.acme.com). A UUID is also accepted. Matched case-insensitively, and scoped to the calling org — a domain belonging to another org is a 404, never a 403.

Request body

Content type: application/json

webhook_url string<uri> required

Must be https, and the host must resolve to a public address. Both are checked here and again at publish time, because what DNS answers changes.

allow_patterns array<string> optional

Local-part globs. OMITTED OR EMPTY DELIVERS EVERY RECIPIENT at the domain — it does not keep the list you had.

spam_posture string enum optional

Defaults to tag.

One of: tag, drop

virus_posture string enum optional

Defaults to drop.

One of: tag, drop

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 The receiving domain, plus the HMAC secret when this call minted it application/json
domain string required
id string<uuid> required
status string enum required

error means a CHECK could not be performed — a resolver failure, an SES call that failed — and is NOT "the record is missing", which is pending_dns. Polling continues either way, so error is never terminal.

One of: pending_dns, verified, error

last_error string optional

The first thing missing or wrong, in plain words. Absent on a verified domain.

region string required

The RECEIVING region — the region of the stack carrying the inbound module, and the region whose SES endpoint your MX must name.

cross_region boolean required

True when the receiving region differs from the account's sending region. A fact to state, never an error: only 22 AWS regions receive mail at all.

mx_verified_at string<date-time> optional
identity_verified_at string<date-time> optional
records array<object> required

The copy-paste DNS instruction set. SendOps will never publish these for you — it holds no DNS credentials — so this list is the whole of what turns pending_dns into verified.

Each entry in records:

type string enum required

One of: MX, CNAME

name string required
value string required
verified boolean required
webhook object | null required

Null when no webhook is configured.

Fields of webhook:

url string<uri> required
allow_patterns array<string> required

Local-part globs — support, ticket-*. EMPTY MEANS EVERY RECIPIENT AT THE DOMAIN IS DELIVERED, which is the opposite of how an empty list usually reads and is the most consequential field on this shape. A recipient that matches nothing in a non-empty list is recorded as no_route.

spam_posture string enum required

tag delivers a message SES marked as spam, with the verdict stated in the payload; drop records the outcome and delivers nothing.

One of: tag, drop

virus_posture string enum required

One of: tag, drop

status string enum required

pending means the routing has not been re-published yet, which normally closes within seconds. invalid means the URL failed re-validation at publish time — most often a host that stopped resolving, or one that now resolves to a private address — so the domain was left out of the routing entirely; error says which.

One of: unset, pending, published, invalid

error string optional
published_at string<date-time> optional
created_at string<date-time> required
hmac_secret string optional

SHOWN ONCE. Present only when this call minted the secret — the first webhook save on a domain. A later save returns the same shape with this field absent, because SendOps holds the secret encrypted and cannot show it twice. Store it when you receive it; if you lose it, rotate.

401 Missing, malformed, or unknown API key application/problem+json
403 Either the credential lacks the required scope (code: invalid_scope), or it is bound to the test environment and this operation is irreversible (code: test_environment_forbidden). Branch on code: the first is fixed by granting the scope, the second only by using a live credential. See the "Live and test credentials" section of the API description. application/problem+json
404 Resource not found application/problem+json
422 A query parameter, path value or body field failed validation. The body is a validation_failed Problem. When the refusal is about a reference — a segment key, template slug, topic or attribute name the organization does not have — it additionally carries validation_code, field, line/column, missing, candidates and next_step, so a client can correct the call without a second round of guessing. See ValidationProblem. application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json