Hardening Email Deliverability: From HTML Rendering Failures to Dark-Theme Tables with CloudFront URL Rewrites

What Happened

A crew notification email for ash scattering uniform requirements was rendered as unreadable cream text on white backgrounds across Gmail clients. Root cause: the email template wrapped content in a <div> with background:#0a1628, which Gmail Web strips at the outer level, leaving foreground text orphaned on the client's default background. This required three coordinated fixes: (1) hardened email template design with table-based layouts, (2) a new public uniforms policy page, and (3) infrastructure changes to ensure reliable domain routing.

Technical Details: The Email Rendering Problem

Gmail Web has well-documented CSS parsing behavior: it strips outer-level <div> styling to prevent CSS injection attacks. Our May 17 template preview file used this structure:

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

When SES delivered this HTML, Gmail's parser removed the outer <div> background. The cream-colored text (#f3efe7) then rendered directly on Gmail's white default pane, making it nearly invisible.

Solution: Table-Wrapped Dark Theme

We rebuilt the template using Gmail-safe patterns:

  • Wrapped all content in a <table bgcolor="#0a1628"> (table backgrounds survive Gmail parsing)
  • Applied bgcolor and color attributes to every <td> with !important flags
  • Added <meta name="color-scheme" content="dark"> in the <head> for clients that support it (Outlook, Apple Mail)
  • Included force-dark CSS hooks for Gmail's dark mode detection

The corrected structure for a cell:

<td bgcolor="#0a1628" style="color:#f3efe7 !important; padding:16px;">
  Your content here
</td>

This approach prioritizes inline attributes over external CSS, ensuring consistent rendering across clients that strip stylesheets or sandboxes.

Workflow & Deployment

Files Created/Modified:

  • /Users/cb/Documents/repos/sites/queenofsandiego.com/uniforms.html — new public uniforms policy page (dark navy #0a1628 with gold accents, two-card layout for ash scattering vs. standard requirements)
  • /tmp/uniform_resend.py — SES sender script with hardened email template and Gmail readability validation
  • /Users/cb/.claude/projects/memory/feedback_email_light_theme.md — documented the light-theme failure case
  • /Users/cb/.claude/projects/memory/MEMORY.md — updated with "outer divs banned for SES sends" rule

Execution:

python /tmp/uniform_resend.py \
  --to-list crew_contacts.json \
  --template hardened_dark_theme.html \
  --validate-gmail-pane \
  --dry-run

Dry-run passed. Live send: 14 crew members via SES (MessageId: 0100019e45b138ad-…). Non-crew notifications sent via SMS to Travis Neel (530-262-5427) per his preference.

Infrastructure: CloudFront Clean URLs & Uptime Monitoring

In parallel, we addressed a separate domain reliability issue on dangerouscentaur.com. The site was returning 404s on certain paths, causing searchability and user experience problems.

CloudFront Function for URL Rewriting:

Created a CloudFront function (not Lambda@Edge—lighter weight, sub-millisecond execution) to rewrite clean URLs:

  • Function name: dc-clean-urls
  • File: /tmp/dc-clean-urls.js
  • Distribution ID: (dangerouscentaur.com CloudFront dist)
  • Trigger: Viewer Request event

Logic: If request path is /hero, rewrite to /hero/index.html before origin fetch. If origin returns 404, serve a fallback that maps to the correct S3 object.

// Simplified logic
if (uri.endsWith('/') === false && !uri.includes('.')) {
  uri += '/index.html';
}
request.uri = uri;
return request;

The function was published to the live distribution via AWS console (no Terraform—rapid iteration needed). Attached to viewer request phase. Verified with test paths including /parallax0, /poster-rack, and deep nav links.

Uptime Lambda for Monitoring:

To catch future 404 regressions, we created an uptime check:

  • Function name: dc_uptime_lambda (Python 3.11)
  • Runtime: Python 3.11
  • IAM role: dc-uptime-monitor-role with DynamoDB write permission to dc-uptime-checks table
  • Invocation: EventBridge rule firing every 1 minute
  • Payload: Checks critical paths (/, /about, /hero, /contact), logs HTTP status and response time to DynamoDB

Created DynamoDB table dc-uptime-checks (on-demand billing, TTL 30 days for cost management):

aws dynamodb create-table \
  --table-name dc-uptime-checks \
  --attribute-definitions AttributeName=timestamp,AttributeType=N \
  --key-schema AttributeName=timestamp,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

Attached EventBridge rule (cron: rate(1 minute)) to invoke the Lambda. Results are tracked on the managercandy dashboard for visibility.

Key Design Decisions

Why Table-Based Email Layout Over CSS? HTML email rendering is fragmented. Tables are the only universally supported layout mechanism. While modern email clients support CSS Grid and Flexbox, Outlook Desktop (still widely used in enterprise) does not. Tables guarantee consistency across 95%+ of clients.

Why CloudFront Function Over Lambda@Edge? CloudFront Functions execute at edge locations with sub-millisecond latency and no cold start. Lambda@Edge has ~50ms baseline. For URL rewriting (a simple string transformation), CloudFront Functions are faster and cheaper.