verify-email-snapshots

Snapshot test email templates using Verify to catch regressions. Validates rendered HTML output matches approved baseline. Works with MJML templates and any email renderer.

25 stars

Best use case

verify-email-snapshots is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Snapshot test email templates using Verify to catch regressions. Validates rendered HTML output matches approved baseline. Works with MJML templates and any email renderer.

Teams using verify-email-snapshots should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/verify-email-snapshots/SKILL.md --create-dirs "https://raw.githubusercontent.com/ComeOnOliver/skillshub/main/skills/Aaronontheweb/dotnet-skills/verify-email-snapshots/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/verify-email-snapshots/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How verify-email-snapshots Compares

Feature / Agentverify-email-snapshotsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Snapshot test email templates using Verify to catch regressions. Validates rendered HTML output matches approved baseline. Works with MJML templates and any email renderer.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Snapshot Testing Email Templates with Verify

## When to Use This Skill

Use this skill when:
- Testing email template rendering for regressions
- Validating MJML templates compile to expected HTML
- Reviewing email changes in code review (diffs are visual)
- Ensuring variable substitution works correctly

**Related skills:**
- `aspnetcore/mjml-email-templates` - MJML template authoring
- `aspire/mailpit-integration` - Test email delivery locally
- `testing/snapshot-testing` - General Verify patterns

---

## Why Snapshot Test Emails?

Email templates are:
1. **Visual** - Small changes can break rendering across clients
2. **Hard to unit test** - Output is complex HTML, not simple values
3. **Prone to regression** - Template changes can have unintended effects

Snapshot testing captures the rendered HTML and fails when it changes unexpectedly.

---

## Installation

```bash
dotnet add package Verify.Xunit  # or Verify.NUnit, Verify.MSTest
```

---

## Basic Email Snapshot Test

```csharp
[Fact]
public async Task UserSignupInvitation_RendersCorrectly()
{
    // Arrange
    var renderer = _services.GetRequiredService<IMjmlTemplateRenderer>();

    var variables = new Dictionary<string, string>
    {
        { "PreviewText", "You've been invited to join Acme Corp" },
        { "OrganizationName", "Acme Corporation" },
        { "InviteeName", "John Doe" },
        { "InviterName", "Jane Admin" },
        { "InvitationLink", "https://example.com/invite/abc123" },
        { "ExpirationDate", "December 31, 2025" }
    };

    // Act
    var html = await renderer.RenderTemplateAsync(
        "UserInvitations/UserSignupInvitation",
        variables);

    // Assert
    await Verify(html, extension: "html");
}
```

This creates `UserSignupInvitation_RendersCorrectly.verified.html` on first run.

---

## Reviewing Email Changes

When a template changes, the test fails with a diff. Review options:

### 1. Visual Diff Tool

```bash
# Configure diff tool (one-time)
dotnet tool install -g verify.tool
verify accept  # Accept all pending changes
verify review  # Open diff tool
```

### 2. Browser Preview

Open the `.received.html` file in a browser to see the actual rendering.

### 3. IDE Integration

Most IDEs show inline diffs for `.verified.html` vs `.received.html` files.

---

## Test Each Template Variant

Create tests for each email template to catch regressions:

```csharp
public class EmailTemplateSnapshotTests : IClassFixture<EmailTestFixture>
{
    private readonly IMjmlTemplateRenderer _renderer;

    public EmailTemplateSnapshotTests(EmailTestFixture fixture)
    {
        _renderer = fixture.Services.GetRequiredService<IMjmlTemplateRenderer>();
    }

    [Fact]
    public async Task WelcomeEmail_NewUser() =>
        await VerifyTemplate("Welcome/NewUser", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "LoginUrl", "https://example.com/login" }
        });

    [Fact]
    public async Task WelcomeEmail_InvitedUser() =>
        await VerifyTemplate("Welcome/InvitedUser", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "InviterName", "Jane Admin" },
            { "OrganizationName", "Acme Corp" }
        });

    [Fact]
    public async Task PasswordReset() =>
        await VerifyTemplate("PasswordReset/PasswordReset", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "ResetLink", "https://example.com/reset/abc123" },
            { "ExpirationMinutes", "30" }
        });

    [Fact]
    public async Task PaymentReceipt() =>
        await VerifyTemplate("Billing/PaymentReceipt", new Dictionary<string, string>
        {
            { "UserName", "John Doe" },
            { "Amount", "$10.00" },
            { "InvoiceNumber", "INV-2025-001" },
            { "Date", "January 15, 2025" }
        });

    private async Task VerifyTemplate(
        string templateName,
        Dictionary<string, string> variables)
    {
        var html = await _renderer.RenderTemplateAsync(templateName, variables);
        await Verify(html, extension: "html")
            .UseMethodName(templateName.Replace("/", "_"));
    }
}
```

---

## Scrubbing Dynamic Values

Some values change between test runs. Scrub them:

```csharp
[Fact]
public async Task EmailWithTimestamp_ScrubsDynamicValues()
{
    var html = await _renderer.RenderTemplateAsync("Welcome", variables);

    await Verify(html, extension: "html")
        .ScrubLinesContaining("Generated at:")
        .ScrubInlineGuids();  // Scrubs GUIDs in URLs
}
```

### Common Scrubbers

```csharp
// Scrub dates
.ScrubLinesContaining("Date:")
.AddScrubber(s => Regex.Replace(s, @"\d{4}-\d{2}-\d{2}", "SCRUBBED-DATE"))

// Scrub URLs with tokens
.AddScrubber(s => Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"))

// Scrub GUIDs
.ScrubInlineGuids()
```

---

## Test Fixture for Email Tests

```csharp
public class EmailTestFixture : IAsyncLifetime
{
    public IServiceProvider Services { get; private set; } = null!;

    public async Task InitializeAsync()
    {
        var services = new ServiceCollection();

        services.AddSingleton<IConfiguration>(new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["SiteUrl"] = "https://example.com"
            })
            .Build());

        services.AddSingleton<IMjmlTemplateRenderer, MjmlTemplateRenderer>();

        Services = services.BuildServiceProvider();

        await Task.CompletedTask;
    }

    public Task DisposeAsync() => Task.CompletedTask;
}
```

---

## Composer Snapshot Tests

Test the full composer output including subject and metadata:

```csharp
[Fact]
public async Task SignupInvitation_ComposesCorrectEmail()
{
    var composer = _services.GetRequiredService<IUserEmailComposer>();

    var email = await composer.ComposeSignupInvitationAsync(
        recipientEmail: new EmailAddress("john@example.com"),
        recipientName: new PersonName("John Doe"),
        inviterName: new PersonName("Jane Admin"),
        organizationName: new OrganizationName("Acme Corp"),
        invitationUrl: new AbsoluteUri("https://example.com/invite/abc123"),
        expiresAt: new DateTimeOffset(2025, 12, 31, 0, 0, 0, TimeSpan.Zero));

    // Verify the full email object (subject, to, body)
    await Verify(new
    {
        email.To,
        email.Subject,
        HtmlBody = email.HtmlBody  // Will be stored as .html extension
    });
}
```

---

## CI Integration

### Fail on Missing Baseline

In CI, fail if no `.verified.html` file exists (prevents accidental acceptance):

```csharp
// In test setup or ModuleInitializer
VerifierSettings.ThrowOnMissingVerifiedFile();
```

### Git Configuration

Add to `.gitattributes` to improve diff handling:

```gitattributes
*.verified.html linguist-language=HTML
*.verified.html diff=html
```

---

## Best Practices

### DO

```csharp
// DO: Test each template variant
[Fact] Task WelcomeEmail_NewUser_RendersCorrectly()
[Fact] Task WelcomeEmail_InvitedUser_RendersCorrectly()

// DO: Use descriptive test names
[Fact] Task PaymentReceipt_WithRefund_ShowsRefundAmount()

// DO: Scrub dynamic values consistently
.ScrubLinesContaining("Generated at:")

// DO: Review diffs carefully before accepting
verify review
```

### DON'T

```csharp
// DON'T: Skip email testing
// DON'T: Auto-accept changes without review
verify accept --all  // Dangerous!

// DON'T: Test only happy path
// DON'T: Ignore snapshot test failures
```

---

## Workflow

1. **Create template** - Write MJML template
2. **Write test** - Add snapshot test with sample variables
3. **Run test** - First run creates `.verified.html`
4. **Review** - Open in browser, verify rendering
5. **Commit** - Include `.verified.html` in source control
6. **Iterate** - Changes fail test, review diff, accept if correct

---

## Resources

- **Verify**: https://github.com/VerifyTests/Verify
- **Verify.Xunit**: https://github.com/VerifyTests/Verify#xunit
- **Diff Tools**: https://github.com/VerifyTests/DiffEngine

Related Skills

email-notify

25
from ComeOnOliver/skillshub

Send SMTP email notifications after Codex completes a task. Use when one Codex or Claude run is finished, or when you need to notify on task completion with device name, project name, status, and summary via email.

quality-verify

25
from ComeOnOliver/skillshub

Verify the final deliverable meets all quality criteria before delivery. Use as the final validation step to ensure the output meets the user's quality standards across all 6 dimensions.

email-handler

25
from ComeOnOliver/skillshub

Create and send transactional emails using React Email. Covers templates, layout integration, and sending logic.

email

25
from ComeOnOliver/skillshub

Email operations skill for sending, fetching, and reading emails via IMAP/SMTP. Uses curl with OpenSSL/LibreSSL for reliable TLS compatibility with Tencent Enterprise Mail and other providers. Credentials are securely stored in macOS Keychain.

Resend — Modern Email API for Developers

25
from ComeOnOliver/skillshub

You are an expert in Resend, the developer-first email API. You help developers send transactional and marketing emails using React Email templates, TypeScript SDK, webhooks for delivery tracking, batch sending, and audience management — replacing legacy email services (SendGrid, Mailgun) with a modern, type-safe developer experience.

React Email — Build Emails with React Components

25
from ComeOnOliver/skillshub

You are an expert in React Email, the library for building responsive HTML emails using React components. You help developers create beautiful, cross-client-compatible email templates with type-safe components, live preview, and integration with email providers (Resend, SendGrid, Postmark, AWS SES) — replacing fragile HTML table layouts with a modern component-based workflow.

Outlook Email

25
from ComeOnOliver/skillshub

## Overview

Email Drafter

25
from ComeOnOliver/skillshub

## Overview

Email Deliverability Debugger

25
from ComeOnOliver/skillshub

## Overview

Verify Skill

25
from ComeOnOliver/skillshub

Run full verification before committing or creating a PR.

verifiedemail-automation

25
from ComeOnOliver/skillshub

Automate Verifiedemail tasks via Rube MCP (Composio). Always search tools first for current schemas.

postgrid-verify-automation

25
from ComeOnOliver/skillshub

Automate Postgrid Verify tasks via Rube MCP (Composio). Always search tools first for current schemas.