DOCS LLMs

Email logos that survive Gmail

RailsFast gives transactional emails their own logo (email_logo_url in config/railsfast/railsfast.yml, separate from the web UI's logo_url), because Gmail destroys a typical web logo in two independent ways, and you'll never see either one coming: browsers and /rails/mailers previews render the broken asset perfectly. The damage only shows in a real Gmail inbox.

This convention comes from production: an app that mailed its web logo (a transparent WebP with a near-black wordmark) saw the email header arrive in Gmail as a solid black slab.

The two failure modes

1. Gmail re-encodes WebP to JPEG, and JPEG has no alpha channel. Gmail doesn't render WebP: it converts the file to JPEG on its image proxy, and since JPEG can't represent transparency, every transparent pixel is flattened to black. A dark wordmark on a black flatten is invisible. Sources: caniemail's WebP support table ("Converts file to jpg"), confirmed by the WebP team themselves.

2. Gmail's mobile dark mode repaints backgrounds, but never touches pixels inside an image. Switching to a transparent PNG fixes the WebP conversion, and then dark mode gets you: Gmail repaints the email's light background dark, your dark logo sits on it with no plate of its own, and disappears. Baking a light background into the image is the documented fix, because image pixels are the one thing dark mode won't repaint.

A dark logo on transparency dies both ways. That's why reusing logo_url for emails is a trap: the web UI wants transparency (navbar, footers, sidebars), and inboxes can't have it.

The convention

Emails get pixels of their own, wired through four pieces:

  • A dedicated config key. email_logo_url under Email Defaults in config/railsfast/railsfast.yml. The web logo_url keeps its transparency; the email logo never inherits it.
  • A fully opaque PNG with the plate baked in. public/email-logo.png ships painted on #f6f6f6, Goodmail's own page background, which is exactly what sits behind the email header. The plate is an invisible seam in light mode and a readable backdrop in dark mode.
  • Served from public/. The asset deploys atomically with the config that points at it, with no CDN upload to forget. config/initializers/goodmail.rb resolves the relative path against the host each environment mails from, so mailer previews work on localhost and production emails get https://yourdomain.com/email-logo.png. An absolute URL (a CDN, say) passes through untouched.
  • ~4× sizing. Goodmail hardcodes the header <img> at 20px tall, so the shipped PNG is 80px tall: 4× for retina screens, with a few pixels of baked-in padding so the plate stays visible around the mark.

Leaving email_logo_url blank skips the email header entirely. It deliberately does not fall back to logo_url, for all the reasons above. The template also pins the asset in a test: if public/email-logo.png ever reintroduces an alpha channel or palette transparency, the suite goes red.

When you rebrand, regenerate the email logo from your master logo with ImageMagick (the same command lives in the railsfast.yml comment, right where you'll need it):

# 4x of Goodmail's 20px header height, opaque, #f6f6f6 plate baked in
magick logo.png -trim +repage -background '#f6f6f6' -alpha remove -alpha off \
  -filter Lanczos -resize x72 -bordercolor '#f6f6f6' -border 12x4 \
  -strip -define png:compression-level=9 public/email-logo.png

Then eyeball it at /rails/mailers/email_branding. The preview renders the shipped email logo, plus your web logo_url for comparison. Remember the preview can't show you Gmail's damage (browsers render transparency fine): to verify for real, send both to a Gmail account and check on mobile with dark mode on.

Checklist

See also