```html

Hardening Email Rendering in SES: From Broken Dark Themes to Table-Based Layouts

A crew-wide email blast for ash scattering uniform requirements arrived unreadable—cream text on white background—because Gmail Web's DOM sanitization stripped the outer <div> background color. This post covers the technical fix, infrastructure changes, and architectural patterns we implemented to prevent future rendering failures in transactional email.

The Problem: CSS Cascade Failure in Gmail Web

On May 17, a uniform requirement email was sent via Amazon SES using an HTML template that wrapped the entire message in:

<div style="background:#0a1628; color:#f3efe7;">
  <!-- content -->
</div>

When Gmail Web rendered this, it stripped the outer <div> wrapper as a sanitization measure. The result: cream-colored text (#f3efe7) landed on Gmail's default white background, rendering the email completely illegible. Fourteen crew members received an email they couldn't read.

Root cause: Gmail Web sanitizes arbitrary <div> styling to prevent phishing and malicious overlays. We relied on an unsupported pattern.

Technical Solution: Table-Based Layout with Inline Style Hardening

The fix involved restructuring the SES email template to use table-based layouts with explicit inline styles on every cell:

<table width="100%" cellpadding="0" cellspacing="0" bgcolor="#0a1628" style="background-color:#0a1628 !important;">
  <tr>
    <td bgcolor="#0a1628" style="background-color:#0a1628 !important; color:#ffffff; padding:20px;">
      <h1 style="color:#d4af37 !important;">Uniform Requirements</h1>
      <!-- content -->
    </td>
  </tr>
</table>

Why this approach:

  • Table-based layouts predate CSS and remain the most reliable rendering method across email clients. Gmail Web preserves table structure and cell attributes.
  • Dual-layer background specification: Both the deprecated bgcolor attribute and inline style ensure fallback coverage across client variants.
  • !important flags override Gmail's own user-level theme preferences that might invert colors in dark mode.
  • Per-cell styling: Nested <td> elements each carry explicit background and text color, preventing inheritance collapse.

Infrastructure Changes

New Files Created

  • /Users/cb/Documents/repos/sites/queenofsandiego.com/uniforms.html — New landing page documenting uniform standards. Deployed to production S3 bucket queenofsandiego-prod-web and distributed via CloudFront (Distribution ID: E2KZXX...). Marked noindex to prevent search indexing of internal documentation.
  • /tmp/uniform_resend.py — Python 3 script using boto3 SES client to resend corrected email with hardened template. Script includes a pre-send Gmail readability gate that validates contrast ratios before dispatch.

Deployment Process

The uniforms page was deployed in two stages:

# Stage 1: Staging environment validation
aws s3 cp uniforms.html s3://queenofsandiego-staging-web/uniforms.html \
  --profile engineering \
  --metadata "version=1,author=cb,timestamp=$(date +%s)"

# Invalidate staging CloudFront (Distribution: E3KZAA...)
aws cloudfront create-invalidation \
  --distribution-id E3KZAA... \
  --paths "/uniforms.html" \
  --profile engineering

# Stage 2: Production deployment
aws s3 cp uniforms.html s3://queenofsandiego-prod-web/uniforms.html \
  --profile engineering \
  --metadata "version=1,author=cb,timestamp=$(date +%s)"

# Invalidate production CloudFront
aws cloudfront create-invalidation \
  --distribution-id E2KZXX... \
  --paths "/uniforms.html" \
  --profile engineering

Email Delivery and Tracking

SES Send Details:

  • Recipient count: 14 crew members
  • SES Message ID: 0100019e45b138ad-...
  • Configuration Set: queenofsandiego-transactional (enables delivery and bounce tracking)
  • Template source: Hardened dark-theme template stored in /Users/cb/.claude/projects/-Users-cb-Documents-repos/memory/feedback_email_light_theme.md

Supplemental contact via SMS: Travis Neel (crew manager who requested no email communication) received an SMS to 530-262-5427 with the uniform rule and direct link to the uniforms page.

Blast Tracking and Documentation

To prevent regression and enable post-mortems, the blast was logged on two internal dashboards:

  • Manager Candy dashboard: Logged with AWS profile engineering using the endpoint pattern: POST /api/blasts with metadata (template version, recipient count, SES message ID)
  • Kanban board: Issue card-t-1faa1eb1 updated at https://progress.queenofsandiego.com/ with root cause analysis and fix verification

Memory and Pattern Codification

To ensure this failure mode doesn't recur, we documented the anti-pattern and fix in markdown memory:

New rule added to team memory: "All SES emails must use table-based layouts with explicit bgcolor attributes on every <td>. Outer <div> wrappers are banned. All color specifications must include !important flags. A Gmail readability gate (contrast ratio validation) is required before any crew-wide blast is sent."

Key Design Decisions

  • Why not CSS frameworks like MJML? MJML would abstract away the table structure and potentially reintroduce the <div> wrapping problem. We chose explicit table markup for maximum control.
  • Why mark the uniforms page as noindex? The page contains internal crew documentation that shouldn't appear in public search results. It's discoverable only via direct link and internal navigation.
  • Why SMS for Travis instead of email? He explicitly requested no email communication. SMS is the appropriate channel for time-sensitive crew coordination.
  • Why deploy to staging first? Allows validation of S3 bucket permissions, CloudFront cache invalidation, and Route53 DNS behavior before production release.

Verification