Plain-text alternative generated for all HTML emails
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:
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
htmlandtextfields. The plain-text body is either automatically generated from the HTML (usinghtml-to-textor equivalent) or maintained as a separate template. Is NOT a pass when thetextfield is set to an empty string. -
Fail criteria: HTML emails are sent without a plain-text alternative. The ESP call includes
htmlbut thetextfield 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:
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 — RFC 2046 §5.1.4 — multipart/alternative MIME subtype
Taxons
History
- 2026-04-18·v1.0.0·Initial import from sending-pipeline-infrastructure·automated