Skip to main content
SenderKit can receive email, not just send it. Every workspace gets a shared receiving domain — {slug}.in.senderkit.email — and you provision addresses on it from the dashboard or the API. Mail sent to an address is parsed, delivered to your backend as a message.received webhook, and optionally forwarded to a real inbox.

How it works

  1. Create an inbound address from the dashboard or POST /v1/inbound/addresses.
  2. Point whatever should reach you — a support alias, a reply-to on an outbound email, a webhook endpoint you hand to a third party — at that address.
  3. When mail arrives, SenderKit parses it and fires a message.received webhook to every endpoint subscribed to that event (or to one specific endpoint, if the address is bound to it).
  4. Optionally, the address also forwards a copy of the mail to a real inbox.

The shared receiving domain

The first time you create an inbound address, SenderKit provisions {slug}.in.senderkit.email for your workspace automatically — there’s no DNS to configure, since it’s a domain SenderKit already owns and receives mail on. To receive on your own domain instead, see Custom domains below.

Custom domains

Instead of (or alongside) the shared domain, you can claim your own domain to receive mail on — e.g. inbound.acme.com. Dashboard: open Inbound, and use the domain picker in New address to add one, or manage existing ones from the domains section at the top of the page. API:
The response carries the DNS records to publish:
  • An MX record on the domain, pointing at SenderKit’s inbound mail host — required for every custom domain.
  • A DKIM TXT record, proving ownership — only when this domain doesn’t already have a verified custom sending domain on the same workspace. If it does, that identity is reused and the MX is all that’s outstanding.
Nothing is received until the records are live and a background sweep flips the domain’s status from pending to verified — the same polling and 72-hour expiry window as custom sending domains. A domain that doesn’t verify in time flips to failed and can be re-claimed. Conflict protection: claiming a domain that already has live MX records pointing somewhere else — a domain actively receiving mail via Google Workspace, for example — is rejected with 409 and code existing_mx, naming the current mail host(s), instead of silently redirecting that domain’s mail to SenderKit. Confirm you want to proceed, then retry the same request with "acknowledgeExistingMx": true. A domain already claimed by another workspace (or already claimed as a sending domain by a different workspace) is rejected as domain_claimed. Deleting a custom domain stops its addresses from receiving mail immediately; the shared domain can’t be deleted. GET /v1/inbound/domains lists every domain on the workspace, including the shared one.

Creating an address

Dashboard: open Inbound in the sidebar and click New address. API:
  • Omit localPart to get an unguessable generated one (rcv-xxxxxxxxxx) — useful when you’re embedding the address somewhere you don’t want a guessable alias, like a reply-to header on an outbound email. Otherwise it’s 1-64 characters of a-z, 0-9, ., _, -, must start and end alphanumeric, and is lowercased automatically.
  • Pass localPart: "*" for a catch-all address that receives mail for every local part on its domain that no exact address already claims — an exact-match address always takes priority over the catch-all, and the catch-all sentinel bypasses the character rules and the reserved-name list. A catch-all counts as one address against the plan cap like any other.
  • domainId mints the address on a verified custom domain (its id from GET /v1/inbound/domains) instead of the shared domain.
  • livemode defaults to true. A test-mode address (false) still receives real mail and fans out to test-mode webhook endpoints, but any configured forwardTo is recorded as a test send rather than actually delivered. Every address — test or live — counts the same toward the plan’s inbound address cap below; only messages received on a test-mode address skip the message quota (see Billing).
  • forwardTo is optional and forwards a copy of every received message to a real inbox. It can’t point at another inbound address — that’s rejected as a mail loop.
  • webhookEndpointId optionally binds the address to one specific webhook endpoint — that endpoint receives message.received even if it isn’t itself subscribed to the event, but it must be active and in the same mode (livemode) as the address. Left unset, message.received events fan out to every active endpoint subscribed to that event in the address’s mode.
  • A few local parts (postmaster, abuse, unsubscribe) are reserved and can’t be claimed.
Creating and deleting addresses and domains, and reading received mail, all require an API key with the inbound scope.

Reading received mail

GET /v1/inbound/messages lists received-message summaries newest first, with limit / before pagination and an optional address filter. GET /v1/inbound/messages/{id} returns the full message — parsed text and html bodies, from/to/cc, raw headers, a strippedReply (the plain-text reply with quoted history and signature stripped for reply-parsing use cases), and scanning verdicts (spam, virus, SPF, DKIM), as reported. Each message also has a status: Only received messages trigger the message.received webhook and any forward. Combined text + html body storage is capped at 256 KB; longer messages are still delivered in full to your webhook endpoint’s payload size limits with truncated: true set.

Attachments and the raw message

Attachments aren’t stored separately from the original message — fetch one at a time from GET /v1/inbound/messages/{id}/attachments/{index} (the index matches attachments[].index on the message), or fetch the full original message/rfc822 source from GET /v1/inbound/messages/{id}/raw. Both require the inbound scope and are only available for 30 days after receipt — afterward they return 410 with code raw_expired.

Billing

A received live message counts against your plan’s monthly message quota, the same pool outbound sends draw from — one received email is one message, just like one outbound send. Test-mode addresses (livemode: false) don’t consume quota. See Messages → Retention for how the quota itself is enforced. Inbound addresses are a separate, smaller plan-limited resource: Creating an address past your plan’s cap returns 403 with code plan_limit.

Deleting an address

Deleting an address doesn’t delete its message history — past received messages stay queryable. Mail sent to a deleted address afterward is simply dropped, the same as mail to any other unmatched recipient.

Webhooks

Verify signatures and handle the message.received payload.

Authentication

The inbound API key scope.