MailSlurp logo

MailSlurp deliverability test API

Documentation navigation
Search documentation

Run MailSlurp deliverability tests across controlled email and phone cohorts, define expected outcomes, and inspect message evidence when a check fails.

Deliverability tests give QA, release, and operations teams a pass/fail view of whether the right messages reached the right inboxes or phone numbers during a launch, canary, or campaign run. They are designed for higher-level verification than a single wait call in one test.

When to use this feature

Use deliverability tests when you need to answer questions like:

  • Did every selected inbox receive the expected email volume?
  • Did every selected phone number receive the expected SMS?
  • Did messages arrive from the expected sender?
  • Did the campaign subject line or recipient matching logic behave the way we intended?

If you only need to wait for one message in one test, use Wait for methods instead.

Typical workflow

  1. Create a test with the right scope, selector, and expectations.
  2. Start the test manually or from its schedule.
  3. Trigger traffic from your app, ESP, or release workflow.
  4. Poll status and review pass/fail results as messages arrive.
  5. Investigate unmatched inboxes or phone numbers before scaling up the send.

deliverability test workflow

Example: create an inbox-scoped test

// Define an inbox-scoped deliverability test with explicit inbox targets and a minimum-count expectation.
const createdTest = await deliverabilityTestController.createDeliverabilityTest({
  createDeliverabilityTestOptions: {
    name: `deliverability-test-${Date.now()}`,
    description: 'admin-sdk deliverability progression test',
    scope: 'INBOX',
    selector: {
      type: 'EXPLICIT',
      entityIds: [inboxA.id!, inboxB.id!]
    },
    expectations: [
      {
        name: 'expect-1-each',
        minCount: 1
      }
    ],
    maxDurationSeconds: 300
  }
});
deliverabilityTestId = createdTest.id;

Example: start a test run

// Start the deliverability test so progress polling can begin.
const startedTest = await deliverabilityTestController.startDeliverabilityTest({
  testId: createdTest.id
});
expect(['SCHEDULED', 'RUNNING']).toContain(startedTest.status);

Scope and selector choices

You run a deliverability test in one scope at a time:

  • Inbox scope for inbound email delivery
  • Phone scope for inbound SMS delivery

Selectors determine which entities participate:

  • all entities in scope
  • explicit entity lists
  • pattern-based selection when you want a naming or addressing convention to define the cohort

Start with a canary cohort before you widen the selector.

What you can validate

Deliverability tests are strongest when you care about expectation-based delivery signals such as:

  • minimum message counts
  • sender matching
  • recipient matching
  • subject matching

They are not a replacement for deeper content review. Pair them with:

Progress and results

During execution you can review:

  • completion percentage
  • matched versus unmatched entities
  • run state such as scheduled, running, paused, completed, failed, or stopped

Example: check for completion

// Poll until the test reaches COMPLETE with all entities matched.
const completedPoll = await waitForDeliverabilityPoll(
  createdTest.id,
  (poll) =>
    poll.test.status === 'COMPLETE' &&
    poll.test.matchedEntities === 2 &&
    poll.test.unmatchedEntities === 0,
  240_000,
  currentStep
);
expect(completedPoll.test.completionPercentage).toEqual(100);

Example: fetch test results

// Fetch final result rows and verify each selected inbox satisfied the expectation.
const finalResults = await deliverabilityTestController.getDeliverabilityTestResults({
  testId: createdTest.id,
  size: 50
});
expect(finalResults.content.length).toEqual(2);
expect(finalResults.test.status).toEqual('COMPLETE');
expect(finalResults.test.matchedEntities).toEqual(2);
expect(finalResults.test.unmatchedEntities).toEqual(0);
expect(finalResults.content.map((result) => result.entityId).sort()).toEqual(
  [inboxA.id!, inboxB.id!].sort()
);

for (const result of finalResults.content) {
  expect(result.matched).toEqual(true);
  expect(result.matchedExpectationCount).toEqual(result.totalExpectationCount);
  expect(result.expectationResults.length).toEqual(1);
  expect(result.expectationResults[0].passed).toEqual(true);
  expect(result.expectationResults[0].actualCount).toBeGreaterThanOrEqual(1);
}

Use unmatched-only views first when triaging failures. That is the fastest way to find which inboxes or phone numbers missed expectations.

  1. Define a small cohort and strict expectations.
  2. Verify sender and subject filters before launch.
  3. Expand the test to the broader release or campaign segment.
  4. Pair the results with monitoring if the send will continue after launch.

Load testing with k6, JMeter and Locust

Separate traffic generation from receipt evaluation. Your load tool drives the application; the MailSlurp test records whether the selected inboxes or phones receive the expected messages. A MailSlurp simulation can exercise delivery infrastructure, while an external send from your application measures that application's full sending path.

Tool Start here What to add for your workload
k6 Email signup example Configure virtual users/stages and thresholds; the sample defaults to one iteration.
JMeter Email load example Set the thread count, ramp and expected recipients for your application.
JMeter SMS Delivery and simulation guide Choose EXTERNAL for your own sender or SIMULATOR for generated traffic; exclude the simulation sender from targets.
Locust Python example Configure user count and spawn rate, and record message-wait duration separately from the signup HTTP request.

Use the dashboard walkthrough to configure the same cohorts manually. Give each worker an inbox or phone lease when testing individual authentication journeys; use explicit cohort IDs when evaluating delivery across a fixed set.

Set the measurement window

Create and start the delivery test before triggering traffic. Set a maximum duration that includes the load ramp and expected delivery lag. Use exact sender, recipient and subject expectations to avoid counting unrelated messages. Keep API rate limits and available message/phone allowances in view when choosing concurrency.

Record request-to-receipt timing in the load harness if latency is an acceptance criterion. A cohort meeting its count expectation does not by itself establish a p95 delivery-latency target. Do not count a successful send API response as proof of receipt.

Pause, stop and export

Use a bounded polling interval; status evaluation has a five-second cache window. Stop a test deliberately when the job is abandoned, and inspect unmatched results before retrying. Export terminal test reports for the build and use CSV for per-entity analysis.

GET /test/deliverability/{testId}/status

Poll load-test status

Poll test progress. Evaluation is throttled with a 5-second cache window to protect backing data stores.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
testIdstring:uuidYes

Responses

StatusSchemaDescription
200DeliverabilityPollStatusResultDtoOK
HTTP examples

HTTP

HTTP
GET /test/deliverability/00000000-0000-4000-8000-000000000000/status HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/status' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/status", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/status")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
GET /test/deliverability/{testId}/results

Get load-test entity results

Get paged per-entity expectation results with optional matched/unmatched filtering.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
testIdstring:uuidYes

Query parameters

NameTypeRequiredDescription
matchedbooleanNo
pageinteger:int32No
sizeinteger:int32No
sortenum: ASC | DESCNoValues: ASC, DESC

Responses

StatusSchemaDescription
200DeliverabilityEntityResultPageDtoOK
HTTP examples

HTTP

HTTP
GET /test/deliverability/00000000-0000-4000-8000-000000000000/results HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/results' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/results", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/results")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
GET /test/deliverability/{testId}/results/export

Export load-test entity results as CSV

Export per-entity deliverability results including expectation-level pass/fail counts. The latest status is evaluated with the same polling safeguards before export.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
testIdstring:uuidYes

Query parameters

NameTypeRequiredDescription
matchedbooleanNo

Responses

StatusSchemaDescription
200ResponseOK
HTTP examples

HTTP

HTTP
GET /test/deliverability/00000000-0000-4000-8000-000000000000/results/export HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/results/export' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/results/export", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/results/export")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
GET /test/deliverability/{testId}/report/export

Export load-test report as PDF

Export a PDF report for a terminal load test (COMPLETE, FAILED, or STOPPED), including configuration, summary outcomes, and detailed entity-level results.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
testIdstring:uuidYes

Responses

StatusSchemaDescription
200ResponseOK
HTTP examples

HTTP

HTTP
GET /test/deliverability/00000000-0000-4000-8000-000000000000/report/export HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request GET \
  --url 'https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/report/export' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/report/export", {
  method: "GET",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/report/export")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("GET", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()

Simulation jobs have their own lifecycle and progress. Keep their ID alongside the delivery test ID, and stop or cancel the simulation when you no longer want it to send traffic.

POST /test/deliverability/{testId}/simulation-jobs

Create deliverability simulation job

Create and start a simulation job for a running load test. Only one active simulation job is allowed per user.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
testIdstring:uuidYes

Request body (required)

FieldTypeRequiredDescription
senderInboxIdstring:uuidNoSender inbox ID for INBOX scope tests
senderPhoneIdstring:uuidNoSender phone ID for PHONE scope tests
emailDeliverabilitySimulationEmailOptionsNo
smsDeliverabilitySimulationSmsOptionsNo
delayMsinteger:int64NoDelay between individual sends in milliseconds
batchSizeinteger:int32NoMaximum sends processed per scheduler batch
sendsPerTargetinteger:int64NoOptional fixed sends per target. If omitted this is derived from test expectations.
Request example
{
  "senderInboxId": "00000000-0000-4000-8000-000000000000",
  "senderPhoneId": "00000000-0000-4000-8000-000000000000",
  "email": {
    "fromOverride": "value",
    "subject": "Hello from MailSlurp",
    "bodyTemplate": "Hello from MailSlurp"
  },
  "sms": {
    "bodyTemplate": "Hello from MailSlurp"
  },
  "delayMs": 250,
  "batchSize": 10
}

Responses

StatusSchemaDescription
200DeliverabilitySimulationJobDtoOK
HTTP examples

HTTP

HTTP
POST /test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*

{}

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
  printf '%b' '{}'
} > "$request_body"
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}' \
  --data-binary "@$request_body")
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const body = Buffer.concat([
  Buffer.from("{}", "utf8")
]);

const response = await fetch("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
  },
  body,
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json",
    "Accept": "*/*"
}
body = b"".join([
    "{}".encode("utf-8")
])
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, body=body, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()
POST /test/deliverability/{testId}/simulation-jobs/{jobId}/cancel

Cancel deliverability simulation job

Cancel a running or paused simulation job.

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
testIdstring:uuidYes
jobIdstring:uuidYes

Responses

StatusSchemaDescription
200DeliverabilitySimulationJobDtoOK
HTTP examples

HTTP

HTTP
POST /test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs/00000000-0000-4000-8000-000000000000/cancel HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: */*

cURL

Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.

cURL
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
  --request POST \
  --url 'https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs/00000000-0000-4000-8000-000000000000/cancel' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'Accept: */*' \
  --output "$response_file" \
  --write-out '%{http_code}')
case "$status" in
  2??) mv "$response_file" 'response.bin' ;;
  *) printf 'HTTP %s\n' "$status" >&2; cat "$response_file" >&2; exit 1 ;;
esac
)

JavaScript (HTTP)

Requires Node.js 22+. Tested with Node.js 22.14.0.

JavaScript (HTTP)
import { writeFile } from "node:fs/promises";

const response = await fetch("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs/00000000-0000-4000-8000-000000000000/cancel", {
  method: "POST",
  headers: {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
  },
  redirect: "manual",
  signal: AbortSignal.timeout(60000),
});
const responseBytes = Buffer.from(await response.arrayBuffer());
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${responseBytes.toString("utf8")}`);
}
await writeFile("response.bin", responseBytes);

Python (HTTP)

Requires Python 3.9+. Tested with Python 3.9.21.

Python (HTTP)
import http.client
from pathlib import Path
from urllib.parse import urlsplit

url = urlsplit("https://api.mailslurp.com/test/deliverability/00000000-0000-4000-8000-000000000000/simulation-jobs/00000000-0000-4000-8000-000000000000/cancel")
headers = {
    "x-api-key": "YOUR_API_KEY",
    "Accept": "*/*"
}
connection_class = http.client.HTTPSConnection if url.scheme == "https" else http.client.HTTPConnection
connection = connection_class(url.hostname, url.port, timeout=60)
try:
    path = (url.path or "/") + ("?" + url.query if url.query else "")
    connection.request("POST", path, headers=headers)
    response = connection.getresponse()
    response_bytes = response.read()
    if not 200 <= response.status < 300:
        raise RuntimeError(f"HTTP {response.status}: {response_bytes.decode('utf-8', errors='replace')}")
    Path("response.bin").write_bytes(response_bytes)
finally:
    connection.close()

Use inbox placement for real provider folder outcomes. Use these load tests for receipt expectations across your selected MailSlurp inboxes and phones.