Automate device renders in CI
Documentation navigation
Render a captured email, wait with a deadline, retain screenshot artifacts and fail incomplete required runs.
Run device rendering after the application has produced and delivered its email. Keep the emailId from the same test so your screenshots show the message your application actually sent.
Set up the test
Install a current mailslurp-client, configure its API key on the test runner and select native target IDs from the catalog. Give the job enough time for message delivery, rendering and artifact downloads. Set the SDK's HTTP timeout above the requested long-wait duration.
Create a private artifact directory before the run. In Node.js, pass an async saveArtifact(name, data) function that writes into that directory using node:fs/promises.writeFile. Playwright users can attach these files to testInfo; other runners can publish the directory after the test, including on failure.
Render, save evidence and assert completion
async function renderEmailInCi(mailslurp, emailId, nativeTargets, saveArtifact) {
const controller = mailslurp.devicePreviewsController;
const created = await controller.createDevicePreviewRun({
emailId,
createDevicePreviewOptions: { nativeTargets },
});
const waited = await controller.waitForDevicePreviewRun({
runId: created.run.runId,
timeoutMillis: 10 * 60 * 1000,
pollIntervalMillis: 5_000,
});
const results = await controller.getDevicePreviewRunResults({
runId: created.run.runId,
});
await saveArtifact(
'render-results.json',
Buffer.from(JSON.stringify(results, null, 2)),
);
for (const screenshot of results.screenshots) {
if (!screenshot.accessUrl) continue;
const response = await fetch(screenshot.accessUrl);
if (!response.ok) throw new Error(`Screenshot download failed: ${response.status}`);
await saveArtifact(
`${screenshot.screenshotId}.png`,
new Uint8Array(await response.arrayBuffer()),
);
}
const incomplete = results.targets.filter((target) => target.status !== 'READY');
if (waited.timedOut) throw new Error('Device preview timed out');
if (waited.run.status !== 'COMPLETE' || incomplete.length) {
throw new Error(`Device preview incomplete: ${incomplete.length} target(s) not ready`);
}
return results;
}
Call renderEmailInCi(mailslurp, email.id, nativeTargets, saveArtifact). This example treats every requested target as required. A PARTIAL_COMPLETE result saves available screenshots and fails the check. A timed-out wait also fails, even when some screenshots exist.
/emails/device-previews/{runId}/wait
Wait for device preview run to complete
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
runId | string:uuid | Yes |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
timeoutMillis | integer:int64 | No | |
pollIntervalMillis | integer:int64 | No |
Responses
| Status | Schema | Description |
|---|---|---|
200 | DevicePreviewRunWaitResult | OK |
HTTP and SDK snippets
HTTP
GET /emails/device-previews/00000000-0000-4000-8000-000000000000/wait?timeoutMillis=value&pollIntervalMillis=value HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
cURL
curl -X GET "https://api.mailslurp.com/emails/device-previews/00000000-0000-4000-8000-000000000000/wait?timeoutMillis=value&pollIntervalMillis=value" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json"
JavaScript SDK
import { Configuration, DevicePreviewsControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const devicePreviewsController = new DevicePreviewsControllerApi(config);
const request = {
"runId": "00000000-0000-4000-8000-000000000000",
"timeoutMillis": null,
"pollIntervalMillis": null
};
const result = await devicePreviewsController.waitForDevicePreviewRun(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.device_previews_controller_api import DevicePreviewsControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
devicePreviewsController = DevicePreviewsControllerApi(api_client)
result = devicePreviewsController.wait_for_device_preview_run("00000000-0000-4000-8000-000000000000", timeout_millis=NaN, poll_interval_millis=NaN)
/emails/device-previews/{runId}/results
Get device preview run results
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
runId | string:uuid | Yes |
Responses
| Status | Schema | Description |
|---|---|---|
200 | DevicePreviewRunResultsDto | OK |
HTTP and SDK snippets
HTTP
GET /emails/device-previews/00000000-0000-4000-8000-000000000000/results HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
cURL
curl -X GET "https://api.mailslurp.com/emails/device-previews/00000000-0000-4000-8000-000000000000/results" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json"
JavaScript SDK
import { Configuration, DevicePreviewsControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const devicePreviewsController = new DevicePreviewsControllerApi(config);
const request = {
"runId": "00000000-0000-4000-8000-000000000000"
};
const result = await devicePreviewsController.getDevicePreviewRunResults(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.device_previews_controller_api import DevicePreviewsControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
devicePreviewsController = DevicePreviewsControllerApi(api_client)
result = devicePreviewsController.get_device_preview_run_results("00000000-0000-4000-8000-000000000000")
Diagnose a failed target
Inspect target status, failure code and run warnings in render-results.json. Differentiate a layout defect in a completed screenshot from a client capture that never completed. Rerun selected failed targets when appropriate and keep the original result with the retry evidence.
/emails/device-previews/{runId}/targets/rerun
Rerun selected device preview targets in the same run
Set failedOnly=false to rerun a successful target. Successful targets consume their configured credit cost again.
Request, parameters, and responses
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
runId | string:uuid | Yes |
Request body (required)
| Field | Type | Required | Description |
|---|---|---|---|
targetIds | string:uuid[] | Yes | Device preview target IDs to rerun within the existing run. Use target IDs from DevicePreviewRunResultsDto.targets. |
failedOnly | boolean | Yes | When true, only selected targets in failed terminal states are rerun. Set false to rerun any selected target; a target that already completed successfully uses its credit cost again. Defaults to true. |
{
"targetIds": [
"00000000-0000-4000-8000-000000000000"
],
"failedOnly": true
}
Responses
| Status | Schema | Description |
|---|---|---|
200 | RerunDevicePreviewTargetsResult | OK |
HTTP and SDK snippets
HTTP
POST /emails/device-previews/00000000-0000-4000-8000-000000000000/targets/rerun HTTP/1.1
Host: api.mailslurp.com
x-api-key: YOUR_API_KEY
Accept: application/json
Content-Type: application/json
{
"targetIds": [
"00000000-0000-4000-8000-000000000000"
],
"failedOnly": true
}
cURL
curl -X POST "https://api.mailslurp.com/emails/device-previews/00000000-0000-4000-8000-000000000000/targets/rerun" \
-H "x-api-key: YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{"targetIds":["00000000-0000-4000-8000-000000000000"],"failedOnly":true}'
JavaScript SDK
import { Configuration, DevicePreviewsControllerApi } from "mailslurp-client";
const config = new Configuration({ apiKey: "YOUR_API_KEY" });
const devicePreviewsController = new DevicePreviewsControllerApi(config);
const request = {
"runId": "00000000-0000-4000-8000-000000000000",
"rerunDevicePreviewTargetsOptions": {
"targetIds": [
"00000000-0000-4000-8000-000000000000"
],
"failedOnly": true
}
};
const result = await devicePreviewsController.rerunDevicePreviewTargets(request);
Python SDK
import mailslurp_client
from mailslurp_client.api.device_previews_controller_api import DevicePreviewsControllerApi
configuration = mailslurp_client.Configuration()
configuration.api_key["x-api-key"] = "YOUR_API_KEY"
with mailslurp_client.ApiClient(configuration) as api_client:
devicePreviewsController = DevicePreviewsControllerApi(api_client)
rerun_device_preview_targets_options = {
"targetIds": [
"00000000-0000-4000-8000-000000000000"
],
"failedOnly": True
}
result = devicePreviewsController.rerun_device_preview_targets("00000000-0000-4000-8000-000000000000", rerun_device_preview_targets_options)
If your job is cancelled, decide whether to cancel the remote run too. A test-runner timeout does not itself prove that remote rendering stopped.
Compare an approved baseline
Store the approved run and client configuration in your test setup. Download baseline and current screenshots, match the same client/platform/color-scheme context, and use your image comparison tool to review the difference. Run-specific target UUIDs identify screenshots within a run; do not assume they are stable across separate runs.
Mask or normalize intentional dynamic content in your comparison process. Require a person to approve a new baseline rather than replacing it automatically whenever the check fails. Use share links and comments for that review, and add Email Audit for links, content and accessibility findings.