MailSlurp logo

Audit email links, images, and HTML with MailSlurp

Documentation navigation
Search documentation

Review message quality, links, images, and sender-facing risks before or after launch.

Email audit is for teams that need a practical quality review before messages reach a large audience. It helps answer whether an email is ready from a content and operational perspective, not just whether the HTML technically exists.

What an email audit should answer

  • Are the important links valid and reachable?
  • Are the images loading the way we expect?
  • Does the message appear complete and trustworthy?
  • Are there obvious issues that should stop a release or campaign launch?

When to run audits

Use email audit:

  • before a release that changes transactional email templates
  • before a marketing or lifecycle campaign goes wide
  • when QA needs a repeatable quality review step
  • after a captured campaign sample reveals something suspicious
  1. Capture or select the message you want to review.
  2. Check links, images, and visible content quality.
  3. Review any issues that would affect trust, conversion, or support load.
  4. Re-run the audit after fixes before approving the send.

API examples

Use these endpoint-focused flows when you want to automate audits in CI or release checks.

Audit campaign HTML in JavaScript, Python, Java or C#

Choose a language once and every example on the page switches to it. Install the SDK for your language, set MAILSLURP_API_KEY, then create a client:

JavaScript

import { Configuration, EmailAuditControllerApi } from 'mailslurp-client';

export function createAuditClient() {
  const apiKey = process.env.MAILSLURP_API_KEY;
  if (!apiKey) throw new Error('Set MAILSLURP_API_KEY');
  return new EmailAuditControllerApi(new Configuration({ apiKey }));
}

Python

import os

import mailslurp_client


def create_client():
    api_key = os.environ.get("MAILSLURP_API_KEY")
    if not api_key:
        raise RuntimeError("Set MAILSLURP_API_KEY")
    configuration = mailslurp_client.Configuration()
    configuration.api_key["x-api-key"] = api_key
    return mailslurp_client.ApiClient(configuration)

Java

import com.mailslurp.apis.*;
import com.mailslurp.clients.ApiClient;
import com.mailslurp.clients.ApiException;
import com.mailslurp.models.*;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

final class AuditSetup {
    static ApiClient createClient() {
        String apiKey = System.getenv("MAILSLURP_API_KEY");
        if (apiKey == null || apiKey.trim().isEmpty()) {
            throw new IllegalStateException("Set MAILSLURP_API_KEY");
        }
        ApiClient client = new ApiClient();
        client.setApiKey(apiKey);
        client.setConnectTimeout(10_000);
        client.setReadTimeout(75_000);
        return client;
    }
}

C#

using System;
using System.Collections.Generic;
using System.Linq;
using mailslurp.Api;
using mailslurp.Client;
using mailslurp.Model;

public static class AuditSetup
{
    public static Configuration CreateConfiguration()
    {
        var apiKey = Environment.GetEnvironmentVariable("MAILSLURP_API_KEY");
        if (string.IsNullOrWhiteSpace(apiKey))
            throw new InvalidOperationException("Set MAILSLURP_API_KEY");
        var configuration = new Configuration { Timeout = TimeSpan.FromSeconds(75) };
        configuration.ApiKey.Add("x-api-key", apiKey);
        return configuration;
    }
}

Audit the campaign HTML before you send it. The result includes a health score and the number of passing and failing checks:

JavaScript

export async function auditHtml(audits, html) {
  // Checks links, images, spelling and content before you send the campaign.
  const audit = await audits.createEmailAudit({
    createEmailAuditOptions: { subject: 'Spring sale: 20% off', fromAddress: 'news@example.com', htmlBody: html },
  });
  const { status, healthScore, passingChecks, failingChecks } = audit.analysis;
  return { auditId: audit.id, status, healthScore, passingChecks, failingChecks };
}

Python

def audit_html(api_client, html):
    # Checks links, images, spelling and content before you send the campaign.
    audit = mailslurp_client.EmailAuditControllerApi(api_client).create_email_audit(
        mailslurp_client.CreateEmailAuditOptions(
            subject="Spring sale: 20% off", from_address="news@example.com", html_body=html
        )
    )
    analysis = audit.analysis
    return audit.id, analysis.status, analysis.health_score, analysis.failing_checks

Java

public static EmailAuditDto auditHtml(ApiClient client, String html) throws ApiException {
    // Checks links, images, spelling and content before you send the campaign.
    EmailAuditDto audit = new EmailAuditControllerApi(client).createEmailAudit(new CreateEmailAuditOptions()
        .subject("Spring sale: 20% off")
        .fromAddress("news@example.com")
        .htmlBody(html)).execute();
    System.out.println("Health score " + audit.getAnalysis().getHealthScore()
        + ", failing checks " + audit.getAnalysis().getFailingChecks());
    return audit;
}

C#

public static EmailAuditDto AuditHtml(Configuration configuration, string html)
{
    // Checks links, images, spelling and content before you send the campaign.
    var audit = new EmailAuditControllerApi(configuration).CreateEmailAudit(new CreateEmailAuditOptions(
        subject: "Spring sale: 20% off", fromAddress: "news@example.com", htmlBody: html));
    Console.WriteLine($"Health score {audit.Analysis.HealthScore}, failing checks {audit.Analysis.FailingChecks}");
    return audit;
}

Read the broken links, broken images and content findings to fix:

JavaScript

export async function readIssues(audits, auditId) {
  const { analysis } = await audits.getEmailAudit({ auditId });
  return {
    brokenLinks: analysis.brokenLinks.map(issue => issue.url),
    brokenImages: analysis.brokenImages.map(issue => issue.url),
    contentIssues: (analysis.contentReview ?? [])
      .filter(check => check.status !== 'PASS')
      .map(check => `${check.title}: ${check.recommendation}`),
  };
}

Python

def read_issues(api_client, audit_id):
    analysis = mailslurp_client.EmailAuditControllerApi(api_client).get_email_audit(audit_id).analysis
    return {
        "broken_links": [issue.url for issue in analysis.broken_links],
        "broken_images": [issue.url for issue in analysis.broken_images],
        "content_issues": [
            f"{check.title}: {check.recommendation}"
            for check in (analysis.content_review or [])
            if check.status != "PASS"
        ],
    }

Java

public static List<String> readIssues(ApiClient client, UUID auditId) throws ApiException {
    EmailAuditAnalysisResult analysis = new EmailAuditControllerApi(client).getEmailAudit(auditId).execute().getAnalysis();
    List<String> issues = analysis.getBrokenLinks().stream()
        .map(issue -> "Broken link: " + issue.getUrl()).collect(Collectors.toList());
    if (analysis.getContentReview() != null) {
        analysis.getContentReview().stream()
            .filter(check -> check.getStatus() != EmailAuditCheck.StatusEnum.PASS)
            .forEach(check -> issues.add(check.getTitle() + ": " + check.getRecommendation()));
    }
    return issues;
}

C#

public static List<string> ReadIssues(Configuration configuration, Guid auditId)
{
    var analysis = new EmailAuditControllerApi(configuration).GetEmailAudit(auditId).Analysis;
    var issues = analysis.BrokenLinks.Select(issue => $"Broken link: {issue.Url}").ToList();
    issues.AddRange(analysis.BrokenImages.Select(issue => $"Broken image: {issue.Url}"));
    issues.AddRange(analysis.SpellingIssues.Select(issue => $"Spelling: {issue.Message}"));
    return issues;
}

List recent audits, for example to show scores in a release check:

JavaScript

export async function listAudits(audits) {
  const recent = await audits.getEmailAudits({ limit: 10 });
  return recent.map(audit => ({ id: audit.id, subject: audit.subject, score: audit.analysis.healthScore }));
}

Python

def list_audits(api_client):
    audits = mailslurp_client.EmailAuditControllerApi(api_client).get_email_audits(limit=10)
    return [(audit.id, audit.subject, audit.analysis.health_score) for audit in audits]

Java

public static List<String> listAudits(ApiClient client) throws ApiException {
    return new EmailAuditControllerApi(client).getEmailAudits().limit(10).execute().stream()
        .map(audit -> audit.getSubject() + " score " + audit.getAnalysis().getHealthScore())
        .collect(Collectors.toList());
}

C#

public static List<string> ListAudits(Configuration configuration)
{
    return new EmailAuditControllerApi(configuration).GetEmailAudits(limit: 10)
        .Select(audit => $"{audit.Subject} score {audit.Analysis.HealthScore}")
        .ToList();
}

The complete audit examples include dependency files for each language. The TypeScript examples below audit an email you have sent, save audits created from content, fetch audits by ID, compare two audits and delete old ones.

Send an email and audit it by email ID

First send a sample email and wait for capture:

await inboxController.sendEmailAndConfirm({
  inboxId: inbox.id!,
  sendEmailOptions: {
    to: [inbox.emailAddress!],
    subject,
    isHTML: true,
    body: '<html><body><h1>Email audit smoke test</h1><p>This message checks email audit endpoints.</p></body></html>'
  }
});

const email = await waitForController.waitForLatestEmail({
  inboxId: inbox.id,
  timeout: 60_000,
  unreadOnly: true
});

Then run a one-shot analysis and persist it:

const oneShotAudit = await emailController.checkEmailAudit1({
  emailId: email.id!
});

const persistedAudit = await emailController.createEmailAuditForEmail({
  emailId: email.id!
});

Create persistent audits directly from content

When you want to evaluate template payloads without first storing an email, create audits directly:

const baselineAudit = await emailAuditController.createEmailAudit({
  createEmailAuditOptions: {
    subject: baselineSubject,
    fromAddress: 'alerts@example.com',
    recipient: 'qa@example.com',
    htmlBody:
      '<html><body><h1>Baseline</h1><p>All systems nominal.</p></body></html>',
    textBody: 'Baseline audit'
  }
});

const currentAudit = await emailAuditController.createEmailAudit({
  createEmailAuditOptions: {
    subject: currentSubject,
    fromAddress: 'alerts@example.com',
    recipient: 'qa@example.com',
    htmlBody:
      '<html><body><h1>Current</h1><p>All systems nominal with updates.</p><a href="https://www.mailslurp.com">MailSlurp</a></body></html>',
    textBody: 'Current audit'
  }
});

List recent audits and fetch one by ID

Use list and get operations to power dashboards or release review screens:

const recentAudits = await emailAuditController.getEmailAudits({
  since: new Date(Date.now() - 10 * 60 * 1000).toISOString(),
  limit: 20
});

const fetchedAudit = await emailAuditController.getEmailAudit({
  auditId: currentAudit.id
});

Compare two audits

Compare baseline vs current versions to quantify quality drift:

const comparison = await emailAuditController.compareEmailAudits({
  auditId: baselineAudit.id,
  otherAuditId: currentAudit.id
});

Delete obsolete audits

Clean up outdated records when they are no longer needed:

await emailAuditController.deleteEmailAudit({
  auditId: baselineAudit.id
});

Best paired tools

Who uses this most

  • QA teams approving release changes
  • lifecycle and CRM teams checking campaign quality
  • platform teams reviewing high-risk transactional template updates

Interpret the score and findings

Use the health score to summarize a run and individual findings to decide what to change. Failures, warnings, manual checks and passes have different meanings. A manual check needs human review; it should not silently count as a pass in a release policy.

Finding Follow-up
Broken link or remote image Open the reported URL and response, fix the destination or asset, then audit a new delivered message.
Unresolved personalization Check the ESP's final rendered values using representative recipient data.
Accessibility or HTML support Review the specific evidence and confirm the affected client with device rendering.
Sender evidence Inspect authentication and domain findings; run placement separately for folder outcomes.

Choose a CI policy

Start with explicit required checks, such as no broken links or images, then add checks that matter to the message. Keep warnings visible without pretending every warning must have the same release consequence. Automated findings support accessibility review; they do not establish standards certification.

const audit = await mailslurp.emailController.createEmailAuditForEmail({ emailId });
const result = audit.analysis;
expect(result.brokenLinks).toHaveLength(0);
expect(result.brokenImages).toHaveLength(0);
expect(result.status).not.toBe('FAILED');

Configure the client using your SDK and retain the audit ID with the test artifacts. These assertions illustrate a minimum policy; inspect other required findings before approving your campaign.

Verify a correction with a second audit

For example, if a CTA points to a deleted landing page, save the initial audit and its failed URL. Correct the template, send a new test through the ESP, and audit that new email ID. Compare the two audits and confirm the link finding is resolved without introducing new image or content problems.

Use device-render review to inspect the corrected design, inbox placement to measure delivery folders, and domain monitoring for persistent sender issues.