MailSlurp logo

Automate device renders in CI

Documentation navigation
Search documentation

Render a captured email, wait with a deadline, retain screenshot artifacts and fail incomplete required runs.

View MarkdownAgent setup

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.

GET /emails/device-previews/{runId}/wait

Wait for device preview run to complete

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
runIdstring:uuidYes

Query parameters

NameTypeRequiredDescription
timeoutMillisinteger:int64No
pollIntervalMillisinteger:int64No

Responses

StatusSchemaDescription
200DevicePreviewRunWaitResultOK
HTTP and SDK snippets

HTTP

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
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

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

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)
GET /emails/device-previews/{runId}/results

Get device preview run results

Request, parameters, and responses

Path parameters

NameTypeRequiredDescription
runIdstring:uuidYes

Responses

StatusSchemaDescription
200DevicePreviewRunResultsDtoOK
HTTP and SDK snippets

HTTP

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
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

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

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.

POST /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

NameTypeRequiredDescription
runIdstring:uuidYes

Request body (required)

FieldTypeRequiredDescription
targetIdsstring:uuid[]YesDevice preview target IDs to rerun within the existing run. Use target IDs from DevicePreviewRunResultsDto.targets.
failedOnlybooleanYesWhen 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.
Request example
{
  "targetIds": [
    "00000000-0000-4000-8000-000000000000"
  ],
  "failedOnly": true
}

Responses

StatusSchemaDescription
200RerunDevicePreviewTargetsResultOK
HTTP and SDK snippets

HTTP

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
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

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

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.