# Plain-text alternative generated for all HTML emails

- **Pattern:** `ab-002448` (`sending-pipeline-infrastructure.template-engine.plain-text-alternative`)
- **Severity:** info
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002448
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002448)

## Why it matters

An HTML-only email displays raw markup in Gmail's push notification preview, in plain-text email clients used in corporate environments, and in assistive technologies that cannot parse HTML. Spam filters from Barracuda, SpamAssassin, and Proofpoint penalize the absence of a multipart/alternative structure because it is a known characteristic of automated spam. RFC-2046 defines the multipart/alternative MIME structure specifically to ensure that all receivers can display a meaningful representation of the message.

## Severity rationale

Info because the consequence is display degradation and minor spam score penalties rather than security exposure or data loss.

## Remediation

Auto-generate the plain-text part from the rendered HTML using `html-to-text` in the send path:

```ts
import { convert } from 'html-to-text'

export function prepareEmailParts(html: string, explicitText?: string) {
  const text = explicitText ?? convert(html, {
    wordwrap: 80,
    selectors: [
      { selector: 'a', format: 'inline', options: { hideLinkHrefIfSameAsText: true } },
      { selector: 'img', format: 'skip' }
    ]
  })
  return { html, text }
}
```

Pass both `html` and `text` to every ESP send call. The `text` field must not be an empty string — that is treated identically to an omitted field by spam filters.

## Detection

- **ID:** `plain-text-alternative`
- **Severity:** `info`
- **What to look for:** Check whether outgoing HTML emails include a plain-text alternative part (multipart/alternative MIME structure). Look at how the email is assembled before it is passed to the ESP SDK. A missing plain-text part causes Gmail and other clients to display raw HTML in notifications and previews, and some spam filters penalize missing plain-text alternatives.
- **Pass criteria:** All HTML emails include a plain-text alternative. Count all ESP send call sites — 100% must set both `html` and `text` fields. The plain-text body is either automatically generated from the HTML (using `html-to-text` or equivalent) or maintained as a separate template. Is NOT a pass when the `text` field is set to an empty string.
- **Fail criteria:** HTML emails are sent without a plain-text alternative. The ESP call includes `html` but the `text` field is omitted or empty.
- **Skip (N/A) when:** The application exclusively sends plain-text emails (no HTML body) — confirmed by the absence of HTML templates.
- **Detail on fail:** `"ESP send call sets html body but omits text field — no plain-text alternative in MIME structure"` or `"text field set to empty string for all sends"`
- **Remediation:** Auto-generate plain-text from the rendered HTML:

  ```ts
  import { convert } from 'html-to-text'

  async function prepareEmailParts(html: string, explicitText?: string) {
    const text = explicitText ?? convert(html, {
      wordwrap: 80,
      selectors: [
        { selector: 'a', format: 'inline', options: { hideLinkHrefIfSameAsText: true } },
        { selector: 'img', format: 'skip' }
      ]
    })

    return { html, text }
  }

  // In the send path:
  const { html, text } = await prepareEmailParts(renderedHtml)
  await esp.send({ to, subject, html, text })
  ```

---

## External references

- external RFC-2046-multipart-alternative — https://www.rfc-editor.org/rfc/rfc2046#section-5.1.4

Taxons: user-experience

HTML version: https://auditbuffet.com/patterns/ab-002448
