---
title: Core concepts
description: Understand workspaces, sending identities, emails, threads, events, and customer spaces.
sidebar:
  label: Concepts
  icon: layers
---

A workspace contains your API keys, sending identities, messages, and configuration. Use the resources below to configure where mail comes from, work with conversations, and handle delivery outcomes.

## The two halves

Everything here divides cleanly. On one side are the objects that establish **who you can send as**: a domain you publish DNS for, or a mailbox a customer authorized. On the other are the objects that carry **what you sent and what came back**: emails, threads, and the events they emit.

You need exactly one thing from the first half before anything in the second half works.

<Columns cols={2}>
  <Column>
    **Identity**

    **Domain** and **Mailbox** — the two ways an address becomes yours to send
    from. Pick one; most products end up with both.
  </Column>
  <Column>
    **Traffic**

    **Email**, **Thread**, **Attachment** — the messages themselves, and the
    conversation each one belongs to.
  </Column>
</Columns>

## Identity

### Domain

A domain you control the DNS for. Creating one returns the records to publish — SPF, DKIM, DMARC, a return path, and an MX record if you want to receive on it — and the domain can send once its DKIM identity is verified. Receiving also requires the inbound MX record.

A domain does not have to be _yours_. Publishing the records at a customer's registrar makes their domain send through you, which is how a product sends as `support@theircompany.com`.

```
dom_… → verified_at, region, records[]
```

**[Domains](/guides/domains)**

Each record, what `diagnostics` reports when one won't resolve, and regions.

### Mailbox

A Gmail or Outlook account a customer authorized over OAuth. No DNS, no verification — they click through a consent screen and the mailbox is live. In exchange you get one address rather than a whole domain, and the authorization can expire.

```
mbx_… → address, provider, app_id, state
```

**[Mailboxes](/guides/mailboxes)**

The connect flow, `ref` for matching a mailbox to your user, and reauth.

:::note
Domain and mailbox aren't "yours" versus "your customer's" — they're two different permissions. A domain needs DNS access; a mailbox needs an OAuth grant. Which one you can get is usually decided by the customer, not by you.
:::

## Traffic

### Email

One message, inbound or outbound, with its addresses, bodies, headers, and attachments. Sending returns one; receiving one fires a webhook that names it.

An email's `status` reports **acceptance**, not delivery — `queued`, `sending`, `sent`, `scheduled`. What actually happened at the far end arrives later, as events.

```
eml_… → thread_id, status, suppressed[], pacing
```

### Thread

The conversation an email belongs to, and the reason this API exists. A reply is matched onto its thread by RFC headers first, then by quoted content — never by subject line alone, which is what makes unrelated "Re: Hi" messages collide elsewhere.

Because the thread is a real object, replying takes the thread ID and your text. The sender, the recipients, the subject, and the `In-Reply-To`/`References` headers are inferred from what's already on the thread.

```
thr_… → subject, mailbox, messages[]
```

**[Threads](/guides/threads)**

How matching decides, and what `threads.reply` fills in for you.

### Attachment

A file on an email. Outbound, you pass base64 content. Inbound, you get metadata plus a `preparation` field — AI Inbx extracts a received PDF to Markdown so an agent can read it without you running a parser.

```
att_… → filename, content_type, size, preparation
```

## Delivery outcomes

### Event and webhook endpoint

Everything that happens after acceptance is an **event**: delivered, bounced, complained, opened, clicked, unsubscribed, plus the inbound `email.received` that starts most applications. A **webhook endpoint** is a URL subscribed to some of them.

Subscribe to webhooks for delivery outcomes as they occur. You can also retrieve an email and inspect its `events` array. A provider accepting a message does not establish that it reached the recipient’s inbox.

```
whk_… → url, subscriptions[], secret
evt_… → type, created_at, data
```

**[Webhooks](/webhooks)**

Endpoints, subscriptions, retries, and replay.

**[Event types](/webhooks/events)**

Each event type and its payload fields.

## Sending controls

Two objects exist only to stop a send that shouldn't happen.

### Suppression

An address that must not be mailed, on a named list. Bounces and complaints create these automatically; unsubscribes and your own calls add more. Every send is checked against the list named by `suppression_key`, plus the org-wide `*` list.

Suppression is why a send can succeed with recipients missing — read `suppressed` on the response.

```
sup_… → address, key, scope, reason
```

### Pacing rule

A ceiling on how fast a lane sends: a window, a rate, a spread. A held message comes back with a non-null `pacing` object naming the rule and projecting when it will go.

Transactional mail shouldn't queue behind a campaign, so a send can set `pacing.skip`.

```
pace_… → window, rate, spread
```

**[Suppressions](/guides/suppressions)**

Lists, scopes, and one-click unsubscribe.

**[Pacing](/guides/pacing)**

Lanes, holds, and releasing a queue by hand.

## Grouping by customer

### Space

Optional. A group inside the workspace — one per customer, typically — that owns domains and mailboxes, and with them every email and thread that went through those. Pacing rules and suppression lists can be put in a space too, and then reach that space alone. API keys and webhook endpoints are workspace-wide.

Mail is never told its space: an email is in the space of the domain or mailbox it went through, and a reply is in its thread's. Any space-aware resource created without a space belongs to the workspace itself and reads `space_id: null`.

```
spc_… → name
```

**[Spaces](/guides/spaces)**

When you need one, and a platform walkthrough end to end.

## A message, through all of it

1. **You send**

    `POST /emails` checks the `from` address against a verified **domain** or an
    active **mailbox**, checks each recipient against the **suppression** lists,
    and asks the **pacing** rules whether the lane has room.

2. **An email and a thread exist**

    You get an **email** back with a `thread_id` — a new **thread** if nothing
    matched, an existing one if `thread_id` was passed. `suppressed` lists who was
    dropped; `pacing` is non-null if the message is held.

3. **Events arrive**

    `email.sent`, then `email.delivered` — or `email.bounced`, which writes a
    **suppression** so the next send to that address never leaves.

4. **They reply**

    Their message is matched onto the same **thread** and arrives as
    `email.received`, carrying the `thread_id` and a `category` that tells you
    how the message was classified. Classification is a signal for your application, not proof of sender identity.

5. **You reply**

    `threads.reply` with the thread ID and your text. Every header that makes it a
    real reply is filled in from the thread.

## Everything has an ID

Prefixed, opaque, stable, safe to store. The prefix is part of the ID — an ID of the wrong type is a `404`, never a silent match against another resource.

**[Conventions](/reference/conventions)**

The full prefix table, pagination, timestamps, request IDs, and idempotency.
