Device targets and reusable profiles
Documentation navigation
Choose supported email clients and reuse target sets across campaigns and automated render runs.
A native target identifies an email-client context. A profile saves a target set so a team can repeat the same review without selecting each client for every campaign.
Discover supported targets
Read the stable target catalog before choosing IDs. Use its client, platform and device metadata to select the contexts your audience uses. The live availability endpoint adds temporary health and queue information; use it to investigate current availability, not to redefine your saved coverage every time a worker is busy.
/device-preview-targets
List device preview targets
Returns visible native device preview target metadata without worker availability, queue, or health fields.
Request, parameters, and responses
Responses
| Status | Schema | Description |
|---|---|---|
200 | DevicePreviewNativeTargetListDto | OK |
HTTP examples
HTTP
GET /device-preview-targets 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.
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
--request GET \
--url 'https://api.mailslurp.com/device-preview-targets' \
--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.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://api.mailslurp.com/device-preview-targets", {
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.
import http.client
from pathlib import Path
from urllib.parse import urlsplit
url = urlsplit("https://api.mailslurp.com/device-preview-targets")
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()
/emails/device-previews/native-targets/availability
Get native device preview target availability
Returns live native device preview target availability for authenticated API clients, including worker health, queue, timeout, and status fields. Use `/device-preview-targets` when you need the stable catalog without transient availability data.
Request, parameters, and responses
Responses
| Status | Schema | Description |
|---|---|---|
200 | DevicePreviewNativeTargetAvailabilityListDto | OK |
HTTP examples
HTTP
GET /emails/device-previews/native-targets/availability 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.
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
--request GET \
--url 'https://api.mailslurp.com/emails/device-previews/native-targets/availability' \
--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.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://api.mailslurp.com/emails/device-previews/native-targets/availability", {
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.
import http.client
from pathlib import Path
from urllib.parse import urlsplit
url = urlsplit("https://api.mailslurp.com/emails/device-previews/native-targets/availability")
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()
Use exact catalog IDs in targets. Avoid legacy provider names and reject unknown IDs before running a large batch. Start with a small representative client set and include mobile, desktop or dark-mode variants when they matter to the message.
The public target catalog lists the currently supported client, platform and appearance combinations. Check the exact combination you need: support for one Outlook or Gmail client does not imply every operating system or dark-mode variant. Use the Python example to inspect the catalog before creating a run.
Account access
An active support-provided demo includes Standard and Premium rendering targets without a paid device-render add-on. Demo access belongs to the account where it was granted, including its organization members; a separate personal account needs its own entitlement. Free accounts use their included credits and Standard targets.
If the API reports that your account has no active subscription providing DevicePreview, first confirm that the API key belongs to the demo account and that its demo period is still active. Support can check the account and extend an expired demo. Include the request path, response code and run ID if one was created; keep the API key private.
Target availability is separate from account access. An unavailable or failed target needs a client-specific retry or investigation, even when your demo entitlement is valid. A run with PARTIAL_COMPLETE has incomplete coverage; inspect its target statuses and retain any available screenshots.
Save a profile
- Open the render-address settings and create a device preview profile.
- Give it a recognizable name, such as Transactional QA.
- Choose a local part and the required targets.
- Send a sample to the profile's render address.
- Confirm the run contains the expected client set.
See the dashboard walkthrough for screenshots. For automation, use the profile API:
/emails/device-previews/profiles
Create a device preview profile
Request, parameters, and responses
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
localPart | string | Yes | |
displayName | string | No | |
options | CreateDevicePreviewOptions | No | |
enabled | boolean | No |
{
"localPart": "value",
"displayName": "Example name",
"options": {
"targets": [
"apple_mail_macos"
],
"allTargets": true,
"qa": {
"emailAudit": true,
"domainMonitor": true,
"senderDomain": "example.com"
},
"folderId": "00000000-0000-4000-8000-000000000000"
},
"enabled": true
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | DevicePreviewProfileDto | OK |
HTTP examples
HTTP
POST /emails/device-previews/profiles HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Content-Type: application/json
Accept: */*
{
"localPart": "value"
}
cURL
Requires cURL 7.76+ and a POSIX shell. Tested with cURL 8.12.1.
(
set -eu
response_file=$(mktemp)
request_body=$(mktemp)
trap 'rm -f "$response_file" "$request_body"' EXIT
{
printf '%b' '{\0012 "localPart": "value"\0012}'
} > "$request_body"
status=$(curl --silent --show-error \
--request POST \
--url 'https://api.mailslurp.com/emails/device-previews/profiles' \
--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.
import { writeFile } from "node:fs/promises";
const body = Buffer.concat([
Buffer.from("{\n \"localPart\": \"value\"\n}", "utf8")
]);
const response = await fetch("https://api.mailslurp.com/emails/device-previews/profiles", {
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.
import http.client
from pathlib import Path
from urllib.parse import urlsplit
url = urlsplit("https://api.mailslurp.com/emails/device-previews/profiles")
headers = {
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
"Accept": "*/*"
}
body = b"".join([
"{\n \"localPart\": \"value\"\n}".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()
/emails/device-previews/profiles
List device preview profiles
Request, parameters, and responses
Responses
| Status | Schema | Description |
|---|---|---|
200 | DevicePreviewProfileDto[] | OK |
HTTP examples
HTTP
GET /emails/device-previews/profiles 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.
(
set -eu
response_file=$(mktemp)
trap 'rm -f "$response_file"' EXIT
status=$(curl --silent --show-error \
--request GET \
--url 'https://api.mailslurp.com/emails/device-previews/profiles' \
--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.
import { writeFile } from "node:fs/promises";
const response = await fetch("https://api.mailslurp.com/emails/device-previews/profiles", {
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.
import http.client
from pathlib import Path
from urllib.parse import urlsplit
url = urlsplit("https://api.mailslurp.com/emails/device-previews/profiles")
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()
/emails/device-previews/profiles/{profileId}
Update a device preview profile
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
profileId | string:uuid | Yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
localPart | string | No | |
displayName | string | No | |
options | CreateDevicePreviewOptions | No | |
enabled | boolean | No |
{
"localPart": "value",
"displayName": "Example name",
"options": {
"targets": [
"apple_mail_macos"
],
"allTargets": true,
"qa": {
"emailAudit": true,
"domainMonitor": true,
"senderDomain": "example.com"
},
"folderId": "00000000-0000-4000-8000-000000000000"
},
"enabled": true
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | DevicePreviewProfileDto | OK |
HTTP examples
HTTP
PUT /emails/device-previews/profiles/00000000-0000-4000-8000-000000000000 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.
(
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 PUT \
--url 'https://api.mailslurp.com/emails/device-previews/profiles/00000000-0000-4000-8000-000000000000' \
--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.
import { writeFile } from "node:fs/promises";
const body = Buffer.concat([
Buffer.from("{}", "utf8")
]);
const response = await fetch("https://api.mailslurp.com/emails/device-previews/profiles/00000000-0000-4000-8000-000000000000", {
method: "PUT",
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.
import http.client
from pathlib import Path
from urllib.parse import urlsplit
url = urlsplit("https://api.mailslurp.com/emails/device-previews/profiles/00000000-0000-4000-8000-000000000000")
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("PUT", 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()
Keep coverage consistent
Review profile changes like other test configuration changes. Record the target set used for a release so a later comparison does not accidentally compare different clients. If a required target fails, inspect or rerun that target; a screenshot from a different client is not replacement evidence.
CI render checks show how to retain partial evidence while failing an incomplete required run. Sharing and review explains how teammates approve the result.
Running this code needs an API key. Get one free