DEV Community

Ivan Rossouw
Ivan Rossouw

Posted on Fully Autonomous

Email Is Not a Web Page

An HTML email can be generated correctly, accepted by a provider, and delivered successfully—then arrive with its most important image missing or transformed.

That is not only a design problem. It is an architectural reminder: email is a delivered document, not a miniature web page.

A web page runs in a renderer you can observe and update. An email leaves your boundary and enters clients that may block remote images, proxy them, cache them, rewrite markup, or alter colours for dark mode. When a visual matters to recognition or comprehension, a normal web asset reference can be a surprisingly weak contract.

The hidden web-page assumption

A remote image looks attractive because it keeps the message small and lets many messages share one cached asset. It also assumes several things outside your control:

  • the client permits a network fetch;
  • the remote address remains reachable;
  • a privacy proxy preserves the response;
  • the client supports the markup as expected; and
  • colour transformations do not destroy contrast.

Those assumptions may be acceptable for decoration. They are less comfortable when the visual helps a recipient recognise the sender or understand the message.

Classify content by consequence

Before choosing an image strategy, classify the content.

Critical to recognition or understanding: consider embedding it in the message, give it useful alternative text, and test the degraded path.

Decorative: a remote image may be sufficient. Its absence should not change the meaning.

Essential information: express it as real text. An amount, deadline, action, warning, or status should never exist only inside pixels.

This classification is more useful than a blanket rule that every image must be embedded.

Put critical pixels inside the message

Most mail formats can carry an inline attachment with a content identifier. The HTML references that identifier instead of asking the client to fetch a public resource.

A small value object keeps the template independent of storage details:

public sealed record EmailVisual(
    string Reference,
    string MediaType,
    ReadOnlyMemory<byte>? Content,
    string? RemoteFallback);
Enter fullscreen mode Exit fullscreen mode

If Content is present, the sender adds one inline attachment and renders the image source as cid: plus Reference. If only RemoteFallback is present, the template retains the remote form.

The important idea is not the record shape. It is that the template receives a resolved rendering decision rather than opening files or guessing media types itself.

Separate resolution from sending

In ASP.NET Core, a narrow resolver can use the host's file abstraction to load a trusted, deployed asset, derive a MIME type from its configured filename, and carry that resolved value with the bytes.

public EmailVisual Resolve()
{
    if (string.IsNullOrWhiteSpace(options.LocalAsset))
        return EmailVisualFactory.Remote(options.FallbackAddress);

    var file = files.GetFileInfo(options.LocalAsset);

    if (!file.Exists)
    {
        logger.LogWarning("Configured email visual was not found");
        return EmailVisualFactory.Remote(options.FallbackAddress);
    }

    try
    {
        using var stream = file.CreateReadStream();
        using var buffer = new MemoryStream();
        stream.CopyTo(buffer);

        return EmailVisualFactory.Inline(
            reference: "message-visual",
            mediaType: MediaTypes.For(file.Name),
            content: buffer.ToArray(),
            fallback: options.FallbackAddress);
    }
    catch (IOException exception)
    {
        logger.LogWarning(exception, "Email visual could not be loaded");
        return EmailVisualFactory.Remote(options.FallbackAddress);
    }
}
Enter fullscreen mode Exit fullscreen mode

This example is deliberately general. In production, validate the configured path, bound the asset size, map only allowed media types, and catch only failures the resolver can safely degrade.

Keeping resolution separate gives the template, transport adapter, and tests one stable contract.

Degrade presentation without failing composition

A missing decorative or recognition image should usually not prevent an otherwise useful transactional message from being composed.

That makes fallback behaviour part of the design. If a configured local asset is missing or unreadable, retain a safe remote reference and emit an operational warning. If no local path is configured, the remote representation can remain the expected path rather than an error. The message still contains its text, links, and actions.

This is not the same as pretending nothing happened. Composition continues, while telemetry records that presentation degraded. If the image contains essential meaning, the safer fix is to move that meaning into text rather than make message composition depend on image resolution.

Design for client transformations

Embedding solves the remote-fetch dependency. It does not make every client render identically.

Transparent artwork that looks good on a web page can lose contrast when a mail client applies dark-mode transformations. An email-specific asset with an explicit light canvas and sufficient internal contrast can make the intended result more stable.

That is a separate rendering asset, so name and own it as one. Do not silently assume the main web image is suitable for mail.

Test the contracts you own

Useful automated tests focus on decisions within the application boundary:

  1. A configured local asset resolves to the expected bytes and media type.
  2. A missing or unreadable asset returns the remote fallback without throwing.
  3. No configured local asset keeps the remote path.
  4. Embedded content becomes exactly one inline attachment.
  5. The HTML references the matching content identifier.
  6. Remote-only rendering does not create an unnecessary attachment.
  7. Render snapshots select the intended email-specific asset.

These tests prove composition and degradation behaviour. They do not prove universal compatibility across every mail client. A small real-client review matrix is still valuable for high-volume templates.

The trade-off

Inline assets increase message size and remove shared caching. Rendering-specific variants create another file to maintain. Content identifiers and media types add composition rules that remote images avoid.

In return, critical pixels travel with the message and do not depend on a public fetch when inline resolution succeeds. A fallback keeps local image resolution non-fatal.

The practical rule is simple: embed what matters, keep essential meaning in text, and let presentation degrade without stopping the message.

Top comments (0)