API Reference

One token. Every inbox.

A small, token-authenticated REST API for reading and clearing test inboxes from your test runner or CI pipeline. No SDK required — plain HTTP in, JSON out.

Base URL: https://sendtrap.dev/api/v1

Interactive reference ↗ OpenAPI 3.1 (YAML) JSON Postman collection

Authentication

Every inbox has its own API token — find it on the inbox's Settings page, under Integration. Send it as a bearer token on every request. There's no separate account-level key and no OAuth flow: one token, scoped to one inbox, full stop.

bash
curl https://sendtrap.dev/api/v1/inbox \
  -H "Authorization: Bearer <your-inbox-token>"

If your HTTP client can't set an Authorization header, send the token as an X-Api-Token header instead. Requests without either return 401.

Rate limits

Scaled by your team's plan, per token. Exceeding it returns 429 Too Many Requests. Free is generous enough for a tight assert-and-poll loop in CI — if you're hitting it, you're probably polling faster than you need to.

Plan Requests/min
Free60
Starter120
Team300
Business600
Enterprise1,200

See pricing for what each plan includes.

Endpoints

Method Path Description
GET/inboxDetails about the authenticated inbox
GET/messagesList messages (paginated, searchable, filterable, optionally blocking)
POST/expectWait, match, assert and diagnose in one request — the recommended testing endpoint
POST/assertBlock until a matching message arrives (or timeout), return pass/fail
POST/messages/{id}/extractPull verification codes, links, addresses and attachments out of a message
GET/messages/{id}Full message detail — headers, HTML, text, links, lint checks
GET/messages/{id}/rawRaw RFC 822 source
GET/messages/{id}/htmlRendered HTML body
GET/messages/{id}/compatibilityHTML Check — email-client HTML/CSS support breakdown (Starter+)
GET/messages/{id}/attachments/{attachment}Download an attachment
PATCH/messages/{id}Mark a message read / unread
DELETE/messages/{id}Delete a single message
DELETE/messagesDelete every message — or only those matching filters

List messages

Returns the inbox's messages, newest first. Standard Laravel pagination — data for the page of results, meta for page/total info.

ParamDescription
searchMatches subject, from address, from name
toRecipient contains this address — checked against the To/Cc headers and the SMTP envelope, so BCC'd recipients match too
test_idExact match against X-Sendtrap-Test-Id (see below)
subject_containsSubject contains this substring — the same filter /assert uses
waitSeconds to block if no message matches yet, up to 30 — see Wait & assert
pagePage number, default 1
per_pageDefault 50

Tag a message so a test can find it without needing a unique recipient address — set X-Sendtrap-Test-Id on the outgoing mail (any mail library lets you add a custom header) and filter by it later. Handy for flows where you don't control the recipient, like an admin-notification email that always goes to a fixed address.

bash
curl "https://sendtrap.dev/api/v1/messages?test_id=ci-run-482&to=live-mail%2Babc%40example.com" \
  -H "Authorization: Bearer <token>"
200 OK
{
  "data": [
    {
      "id": 33,
      "test_id": "ci-run-482",
      "from_address": "hello@acmeanvils.com",
      "from_name": "Acme Anvils",
      "to": [{ "name": null, "address": "live-mail+abc@example.com" }],
      "envelope_to": ["live-mail+abc@example.com"],
      "subject": "Welcome to Acme Anvils Ltd",
      "size": 7146,
      "is_read": false,
      "has_attachments": false,
      "has_unresolved_merge_tags": false,
      "received_at": "2026-07-13T12:46:08+00:00"
    }
  ],
  "links": { "first": "...", "last": "...", "prev": null, "next": null },
  "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 }
}

envelope_to is the SMTP RCPT TO list, captured independently of the To/Cc headers — it's the only reliable way to see a BCC'd recipient, since BCC is invisible in headers by definition. has_unresolved_merge_tags flags a message whose body still contains an unresolved {{ tag }} or %tag% placeholder — see the detail response below for the full list.

Wait & assert

Mail arrives asynchronously, so a naive first request often runs before it lands. Instead of sleep-polling yourself, add wait=<seconds> to GET /messages — if nothing matches yet, the request blocks (checking every ~250ms–1s) until a match arrives or the timeout hits, capped at 30s. One request, no sleep loop, no extra round trips.

bash
curl "https://sendtrap.dev/api/v1/messages?test_id=ci-run-482&wait=15" \
  -H "Authorization: Bearer <token>"

POST /assert takes the same matching idea further: give it a condition, it waits for a match (or the timeout) and always returns 200 with a pass/fail flag in the body — an unmatched assertion is an expected test outcome, not an HTTP error, so you don't need special-case error handling in your assertion library.

bash
curl -X POST https://sendtrap.dev/api/v1/assert \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"test_id": "ci-run-482", "subject_contains": "Welcome", "timeout": 10}'
200 OK
{
  "matched": true,
  "message": { "id": 33, "test_id": "ci-run-482", "subject": "Welcome to Acme Anvils Ltd", "..." : "..." }
}

/assert accepts the same filters as List messages (search, to, test_id, subject_contains) plus timeout (seconds, capped at 30, omit or 0 for an instant check with no blocking). Both endpoints share a tighter rate limit than ordinary API calls — 15 wait/assert requests per minute per token, on every plan — because a blocking request can hold a connection open for its full timeout, so keep parallel wait/assert calls modest (a handful per token, not one per test in a large matrix).

POST /expect is the richer successor: it separates match conditions (which message are we waiting for?) from assert conditions (is its content right?), so a miss tells you whether no mail arrived, mail arrived but didn't match, or the right mail arrived with the wrong content — with per-condition pass/fail and safe actual values. Conditions cover subject, recipients, envelope, bodies, headers, links, attachments and quality checks. An optional extract object additionally pulls named values — verification codes, links, addresses, attachments — out of the matched message in the same request: see Extract values.

bash
curl -X POST https://sendtrap.dev/api/v1/expect \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "match":  [{"field": "to", "op": "contains", "value": "alice@example.com"},
               {"field": "subject", "op": "contains", "value": "Welcome"}],
    "assert": [{"field": "links", "op": "matches", "value": "example\\.com/verify\\?code=\\d+"}],
    "scope":  {"test_id": "ci-run-482"},
    "wait":   {"timeout_ms": 10000},
    "mode":   "strict"
  }'

In strict mode an unmet expectation returns 422 with the full diagnostic body, so a plain HTTP-error check fails the CI step; the default report mode always returns 200. The complete field and operator matrix is in the interactive reference.

min_compatibility_score (0–100, Starter plan and above) additionally requires the matched message's HTML Check compatibility ratio to be at or above this value — handy for gating a CI run on email-client compatibility, not just on the message arriving. Only the matched message is checked (not the whole inbox), so this adds at most one HTML Check's worth of latency to the request.

Extract values

Signup-verification, password-reset and magic-link tests all end the same way: fishing a code or link out of an email. Named extractors do that server-side, so your test never parses MIME or regexes HTML. Add an extract object to POST /expect and matching + extraction happen atomically in one request — wait for the mail, get the code back:

bash
curl -X POST https://sendtrap.dev/api/v1/expect \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "match":   [{"field": "to", "op": "contains", "value": "alice@example.com"},
                {"field": "subject", "op": "contains", "value": "Verify"}],
    "extract": {
      "code":        {"type": "code", "near": "verification code"},
      "verify_link": {"type": "link", "path_prefix": "/verify"}
    },
    "wait": {"timeout_ms": 10000},
    "mode": "strict"
  }'
200 OK (excerpt)
{
  "matched": true,
  "status": "matched",
  "extract": {
    "code":        { "found": true, "status": "found", "value": "482913", "source": "text",
                     "context": "Your verification code is 482913. It expires in 10 minutes." },
    "verify_link": { "found": true, "status": "found",
                     "value": { "url": "https://app.example.com/verify?token=abc123", "text": "Verify my account" } }
  }
}

Extraction is part of the expectation: a missing value keeps the wait polling (a later message can still satisfy everything at once) and otherwise reports status: extraction_failed — in strict mode that's a 422, so the CI step fails. Mark an extractor "optional": true to keep a miss from failing the run. The same extractors also run against a message you already have, via POST /messages/{id}/extract.

Five extractor types:

Type What it does Options
codeVerification-code helper — finds a standalone token in the visible text (never inside a longer id or URL hash). With near, the token closest to the anchor phrase wins.length (4–12, default 6), charset (digits | letters | upper | alphanumeric), near, from
linkSelects a link from the HTML body — criteria AND together, links are returned, never fetched. Relative URLs stay relative unless the mail declares a valid absolute <base href>.url, host, path_prefix, query_param, text_contains, matches
regexBounded regex capture (first capture group, else the whole match). Patterns are server-delimited — input never chooses modifiers.pattern, from (text | html | subject | header.<Name>)
addressAn {address, name} pair from the headers or the SMTP envelope — envelope_to catches BCC-only recipients.field (from | to | cc | envelope_from | envelope_to), matches
attachmentAttachment metadata (id, filename, content type, size, checksum) plus its authenticated download URL — bytes are never inlined.filename, filename_contains, matches, content_type (wildcard subtype: application/*)

Every extractor takes select (first | last | all). Without it, one match is returned directly and several distinct matches come back as status: ambiguous with the candidate list — the server never guesses which value your test meant. Results are typed and diagnosable: found, the source field searched, a bounded context excerpt, and a matches count. Caps: 10 extractors per request, 256-byte regexes. The full option matrix is in the interactive reference.

Get a message

Full detail for one message — parsed HTML and text bodies, all headers, and attachment metadata.

bash
curl https://sendtrap.dev/api/v1/messages/33 \
  -H "Authorization: Bearer <token>" | jq '.data.subject'
200 OK
{
  "data": {
    "id": 33,
    "inbox_id": 15,
    "message_id": "95024650-...@example.com",
    "test_id": "ci-run-482",
    "envelope_from": "bounce@acmeanvils.com",
    "envelope_to": ["test@example.com"],
    "from_address": "hello@acmeanvils.com",
    "from_name": "Acme Anvils",
    "to": [{ "name": "Test User", "address": "test@example.com" }],
    "cc": [],
    "subject": "Welcome to Acme Anvils Ltd",
    "size": 7146,
    "is_read": false,
    "has_html": true,
    "has_text": true,
    "has_attachments": false,
    "has_unresolved_merge_tags": false,
    "unresolved_merge_tags": [],
    "received_at": "2026-07-13T12:46:08+00:00",
    "html": "<html>...</html>",
    "text": "Welcome...",
    "links": ["https://acmeanvils.com/verify?token=abc123"],
    "checks": [
      { "key": "missing_text_part", "passed": true, "severity": "warn" },
      { "key": "oversized_html", "passed": true, "severity": "warn" },
      { "key": "missing_list_unsubscribe", "passed": true, "severity": "info" },
      { "key": "from_address_present", "passed": true, "severity": "error" }
    ],
    "headers": [{ "name": "Subject", "value": "Welcome to Acme Anvils Ltd" }],
    "attachments": [],
    "urls": {
      "raw": "https://sendtrap.dev/api/v1/messages/33/raw",
      "html": "https://sendtrap.dev/api/v1/messages/33/html"
    }
  }
}

links is every href pulled from the HTML body — handy for asserting a verification link's full shape (including query string) without wrestling with &amp;-encoded entities in the raw HTML, or for feeding it back into a test to complete a verification round-trip. checks is a lint report — each entry's passed is false when that check fails; treat it as a heuristic, not a guarantee (broken-image-URL checking isn't included, since that would mean this service fetching arbitrary remote URLs on your behalf).

Messages belonging to a different inbox than the one your token authenticates always return 404 — never a 403, so you can't probe for the existence of IDs outside your inbox.

HTML Check Starter+

Checks a message's HTML/CSS against caniemail.com's email-client feature-support data and flags anything unsupported or partially supported, and in which clients. Computed on first request and cached — later requests for the same message return instantly unless the underlying support data has since been refreshed. The HTML Check tab in the dashboard is available on every plan; this API endpoint (plus the checks[] summary entry below and assert's min_compatibility_score) requires Starter or above.

bash
curl https://sendtrap.dev/api/v1/messages/33/compatibility \
  -H "Authorization: Bearer <token>"
200 OK
{
  "status": "ok",
  "compatibility_ratio": 76.5,
  "issues": [
    {
      "feature_id": "css-gap",
      "title": "gap, column-gap, row-gap",
      "category": "css",
      "severity": "error",
      "unsupported_clients": [
        { "client": "outlook", "platform": "windows", "support": "n", "note": null },
        { "client": "yahoo", "platform": "desktop-webmail", "support": "n", "note": null }
      ]
    }
  ],
  "checked_at": "2026-07-13T12:47:03+00:00"
}

compatibility_ratio is the percentage of distinct HTML/CSS features detected in the message that are fully supported across a fixed reference set of major clients (Apple Mail, Gmail, Outlook, Yahoo and a few others) — equally weighted, not market-share weighted, since no reliable market-share dataset exists. Treat it as a rough filter for CI gating (see min_compatibility_score in Wait & assert), not a precise "% of your subscribers will see this correctly" figure.

On plans below Starter, this endpoint returns 403, and the checks[] array on Get a message simply omits the html_compatibility entry — it's never forced to compute just because you fetched a message, so it only appears once something (the dashboard tab or this endpoint) has actually run the check.

Raw & rendered HTML

urls.raw and urls.html on a message's detail response are ready-to-fetch links (same bearer token required) — useful if you want the full RFC 822 source or a sandboxed-render-ready HTML string without re-fetching the message detail.

bash
curl https://sendtrap.dev/api/v1/messages/33/raw \
  -H "Authorization: Bearer <token>"

Attachments

Each attachment listed on a message detail response includes its own bearer-token-authenticated url — fetch it directly to get the file bytes. It also includes a checksum (sha256 of the raw content) and content_type, so you can assert an attachment is present and non-trivial without downloading it.

bash
curl https://sendtrap.dev/api/v1/messages/33/attachments/9 \
  -H "Authorization: Bearer <token>" \
  -o invoice.pdf

Mark as read

PATCH with an is_read boolean (defaults to true).

bash
curl -X PATCH https://sendtrap.dev/api/v1/messages/33 \
  -H "Authorization: Bearer <token>" \
  -d is_read=true

Delete messages

Delete one message by ID, clear the whole inbox, or — by adding any of the List messages filters (search, to, test_id, subject_contains) — delete only the matching messages. Filtered deletes are what you want on a shared inbox: each test run cleans up its own mail and leaves everyone else's alone.

one message
curl -X DELETE \
  https://sendtrap.dev/api/v1/messages/33 \
  -H "Authorization: Bearer <token>"
this run's messages only
curl -X DELETE \
  "https://sendtrap.dev/api/v1/messages?test_id=ci-run-482" \
  -H "Authorization: Bearer <token>"
# → { "deleted": 3 }
whole inbox
curl -X DELETE \
  https://sendtrap.dev/api/v1/messages \
  -H "Authorization: Bearer <token>"
# → { "deleted": 12 }

Reserve the unfiltered form for an inbox only one runner uses at a time — it deletes everything, including messages a parallel run is still asserting on.

Errors

StatusMeaning
401Missing or invalid token
403Request IP not on the inbox's allowlist (if configured), or a Starter+ feature called on a plan below Starter
404Message/attachment not found, or belongs to a different inbox
429Rate limit exceeded — see plan limits above

CI recipe

Mail arrives asynchronously, so don't assume it's there the instant your app finishes the request. Use /expect instead of hand-rolling a sleep loop — one request, no flaky timing, and a tagged test_id means parallel runs sharing an inbox can't see each other's mail even without unique recipient addresses.

expect-welcome-email.sh
# tag the outgoing mail with X-Sendtrap-Test-Id: $CI_RUN_ID, then — strict mode
# turns an unmet expectation into a non-2xx, so --fail-with-body both fails the
# step and prints the diagnostic explaining exactly what didn't match. The
# `|| status=$?` captures the verdict without tripping `set -e`, so cleanup
# below always runs and the step still fails at the end:
status=0
curl -sS --fail-with-body -X POST https://sendtrap.dev/api/v1/expect \
  -H "Authorization: Bearer $SENDTRAP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "match":  [{"field": "test_id", "op": "equals", "value": "'"$CI_RUN_ID"'"},
               {"field": "subject", "op": "contains", "value": "Welcome to Acme Anvils Ltd"}],
    "assert": [{"field": "has_unresolved_merge_tags", "op": "equals", "value": false}],
    "wait":   {"timeout_ms": 10000},
    "mode":   "strict"
  }' || status=$?

# clean up this run's mail — the filter leaves parallel runs' messages intact,
# and this runs on failure too
curl -s -X DELETE "https://sendtrap.dev/api/v1/messages?test_id=$CI_RUN_ID" \
  -H "Authorization: Bearer $SENDTRAP_TOKEN"

exit $status

Store the token as a CI secret (e.g. SENDTRAP_TOKEN) — never commit it. Give each parallel test environment (staging, PR previews, per-branch) its own inbox and its own token so runs can't see each other's mail — or, if provisioning a separate inbox per run isn't practical, a unique test_id per run does the same job inside a single shared inbox.

The examples below all follow the same three-part pattern: scope each test to its own mail (a unique recipient address per test, or a test_id when you don't control the recipient), expect with one server-side /expect call instead of a sleep loop, and clean up with the same filter in teardown — which runs on failure too, so a red test can't leak messages into the next one. If a raw POST is awkward in your tooling, GET /messages?to=…&wait=10 gives you the same server-side wait on a plain GET.

PHPUnit (Laravel)

One /expect call replaces the poll loop — match conditions find this test's message, assert conditions verdict its content — and the filtered tearDown() delete removes only this test's mail, safe even when several suites share the inbox.

tests/Feature/WelcomeEmailTest.php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class WelcomeEmailTest extends TestCase
{
    // unique recipient per test = this test's mail is identifiable,
    // even when parallel runs share the inbox
    protected string $email;

    protected function setUp(): void
    {
        parent::setUp();
        $this->email = 'welcome-'.Str::uuid().'@example.com';
    }

    protected function tearDown(): void
    {
        // delete only this test's mail — runs on failure too, so a red
        // test can't leak messages into the next run
        $this->sendtrap()->delete('/messages?to='.$this->email);
        parent::tearDown();
    }

    protected function sendtrap(): \Illuminate\Http\Client\PendingRequest
    {
        return Http::withToken(config('services.sendtrap.token'))
            ->baseUrl('https://sendtrap.dev/api/v1');
    }

    public function test_it_sends_a_welcome_email(): void
    {
        User::factory()->create(['email' => $this->email]);

        // one request: waits server-side (up to 10s), matches, and asserts content
        $result = $this->sendtrap()->post('/expect', [
            'match' => [
                ['field' => 'to', 'op' => 'contains', 'value' => $this->email],
                ['field' => 'subject', 'op' => 'equals', 'value' => 'Welcome to Acme Anvils Ltd'],
            ],
            'assert' => [
                ['field' => 'has_unresolved_merge_tags', 'op' => 'equals', 'value' => false],
            ],
            'wait' => ['timeout_ms' => 10000],
        ])->json();

        // status explains a failure: no_candidates / no_match / assertions_failed
        $this->assertSame('matched', $result['status'], json_encode($result['conditions']));
    }
}

Jest / Vitest (Node.js)

welcome-email.test.js
const sendtrap = (path, opts = {}) =>
  fetch(`https://sendtrap.dev/api/v1${path}`, {
    ...opts,
    headers: {
      Authorization: `Bearer ${process.env.SENDTRAP_TOKEN}`,
      'Content-Type': 'application/json',
    },
  }).then((r) => r.json());

// unique recipient per test — identifiable even on a shared inbox
const email = `welcome-${crypto.randomUUID()}@example.com`;

afterEach(async () => {
  // delete only this test's mail — runs on failure too
  await sendtrap(`/messages?to=${email}`, { method: 'DELETE' });
});

test('sends a welcome email', async () => {
  await createUser({ email });

  // one request: waits server-side (up to 10s), matches, and asserts content
  const result = await sendtrap('/expect', {
    method: 'POST',
    body: JSON.stringify({
      match: [
        { field: 'to', op: 'contains', value: email },
        { field: 'subject', op: 'equals', value: 'Welcome to Acme Anvils Ltd' },
      ],
      assert: [{ field: 'has_unresolved_merge_tags', op: 'equals', value: false }],
      wait: { timeout_ms: 10000 },
    }),
  });

  // status explains a failure: no_candidates / no_match / assertions_failed
  expect(result.status).toBe('matched');
});

pytest (Python)

test_welcome_email.py
import os, uuid, requests, pytest

BASE = "https://sendtrap.dev/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SENDTRAP_TOKEN']}"}

@pytest.fixture
def test_email():
    # unique recipient per test — identifiable even on a shared inbox
    email = f"welcome-{uuid.uuid4()}@example.com"
    yield email
    # delete only this test's mail — runs on failure too
    requests.delete(f"{BASE}/messages", params={"to": email}, headers=HEADERS)

def test_sends_a_welcome_email(test_email):
    create_user(email=test_email)

    # one request: waits server-side (up to 10s), matches, and asserts content
    result = requests.post(f"{BASE}/expect", headers=HEADERS, json={
        "match": [
            {"field": "to", "op": "contains", "value": test_email},
            {"field": "subject", "op": "equals", "value": "Welcome to Acme Anvils Ltd"},
        ],
        "assert": [{"field": "has_unresolved_merge_tags", "op": "equals", "value": False}],
        "wait": {"timeout_ms": 10000},
    }).json()

    # status explains a failure: no_candidates / no_match / assertions_failed
    assert result["status"] == "matched", result["conditions"]

Signup verification / password reset / magic link (Playwright)

The flows that used to need custom email parsing are one extract object away: wait for the mail and get the OTP code or magic link back, typed, in the same request — then drive the browser with it. The same shape covers password resets (extract the reset link) and invoice mails (extract the attachment's download URL).

signup-verification.spec.js
test('verifies a new account with the emailed code', async ({ page }) => {
  await page.goto('/signup');
  await page.fill('#email', email);
  await page.click('#submit');

  // one request: wait for the mail, extract the code and the magic link
  const result = await sendtrap('/expect', {
    method: 'POST',
    body: JSON.stringify({
      match: [{ field: 'to', op: 'contains', value: email }],
      extract: {
        code: { type: 'code', near: 'verification code' },
        link: { type: 'link', path_prefix: '/verify' },
      },
      wait: { timeout_ms: 10000 },
      mode: 'strict',
    }),
  });

  // typed values, no email parsing: submit the OTP…
  await page.fill('#otp', result.extract.code.value);
  // …or follow the magic link directly
  // await page.goto(result.extract.link.value.url);
});

Coming from Mailtrap

Migrating a Mailtrap Email Sandbox test helper? Every endpoint you're already calling has a compatible alias under /api/sandboxes/{sandbox}/... — swap the base URL and token and it should just work.

before → after
# Mailtrap
curl https://sandbox.api.mailtrap.io/api/sandboxes/12345/messages \
  -H "Api-Token: <token>"

# Sendtrap — same shape, your inbox's token, sandbox id is ignored
curl https://sendtrap.dev/api/sandboxes/12345/messages \
  -H "Authorization: Bearer <your-inbox-token>"

The {sandbox} segment is accepted but not checked — your bearer token already scopes every request to one inbox, so whatever sandbox ID your old script has hardcoded is fine to leave in place.

Method Path Mailtrap equivalent
GET/sandboxes/{s}/messagesGet Messages (search, page, last_id)
GET/sandboxes/{s}/messages/{id}Show Email Message
PATCH/sandboxes/{s}/messages/{id}Update Message — {"message":{"is_read":true}}
DELETE/sandboxes/{s}/messages/{id}Delete Message
GET.../messages/{id}/body.txtGet Text Message Body
GET.../messages/{id}/body.htmlGet Formatted HTML Message
GET.../messages/{id}/body.htmlsourceGet HTML Message Source
GET.../messages/{id}/body.rawGet Raw Message Body
GET.../messages/{id}/body.emlGet Message as EML
GET.../messages/{id}/mail_headersGet Mail Headers
GET.../messages/{id}/attachmentsGet Attachments
GET.../attachments/{id}Get Single Attachment
GET.../attachments/{id}/download(file bytes, not just metadata)
PATCH/sandboxes/{s}/cleanClean Sandbox
PATCH/sandboxes/{s}/all_readMark All as Read

Not implemented — these have no real equivalent in Sendtrap's model, so we don't fake them: message templates (template_id/template_variables), HTML client-compatibility analysis, spam/blacklist reports, message forwarding, POP3 access, and account-level project/sandbox management (create, list, or delete sandboxes) — a Sendtrap token is already scoped to one inbox, so there's nothing to list or switch between.

Ready to wire it up?

Create a free inbox, grab its token from Settings, and you're making assertions in CI within minutes.

Create your inbox

No credit card required