Email Sections
Overview
Midwest ships a library of email section components — headers, heroes, receipts,
verification codes, order summaries, footers — for building transactional (and light
marketing) email. They are a separate library from the web components under the
Midwest::Email:: namespace, called through midwest_email_* helpers inside a mailer view.
They exist because the web components cannot work in email: an inbox never loads Midwest's stylesheet, so every rule is inlined at render time, tables carry the layout, and buttons are VML "bulletproof" for Outlook. The whole library is deliberately walled off from the CSS/JS build — it ships no stylesheet and no JavaScript.
Browse them as real emails at /rails/mailers in development, or as an isolated,
retheme-able gallery in Lookbook (the "Emails → Sections" previews).
A complete example
A mailer view is a midwest_email document with sections dropped inside it:
<%# app/views/user_mailer/receipt.html.erb %><%= midwest_email(preheader: "Your receipt — $388.80", title: "Receipt") do %> <%= midwest_email_header(logo_url: image_url("logo.png"), logo_width: 120) %> <%= midwest_email_content(heading: "Thanks for your order") do %> <%= midwest_email_text("Order #1234 is confirmed.") %> <% end %> <%= midwest_email_receipt( items: @order.line_items.map { { name: _1.name, qty: _1.quantity, amount: _1.total } }, subtotal: @order.subtotal, tax: @order.tax, total: @order.total ) %> <%= midwest_email_cta("View your order", order_url(@order)) %> <%= midwest_email_footer( address: "Acme Inc, 123 Main St", unsubscribe_url: unsubscribe_url(@user) ) %><% end %>The helpers are available in mailer views automatically — Midwest includes them into
ActionMailer. They are not in scope in regular controller views, and the web
midwest_* helpers are not in scope inside an email.
Colours follow your theme
Email colours resolve through the same Midwest::Theme the web .theme-* classes are
generated from, so an email and a page rendered from the same theme are the same colour.
Set a brand theme once:
# config/initializers/midwest.rbMidwest.configure do |config| config.email.theme = "#7c3aed" # a named theme (e.g. "green") or any hex config.email.font_family = :sans # a token keyword or a raw stack config.email.dark_mode = trueendA custom hex generates a full scale automatically, exactly as the colour picker does — no
second configuration. Components never name a colour directly; they name a role
(:body, :accent, :link, …) that resolves against the theme. A theme: on
midwest_email overrides it per message, so multi-tenant apps can theme each email.
Custom fonts
Email web fonts are a progressive enhancement. Apple Mail, iOS Mail, Outlook for Mac,
and Samsung Mail render them; Gmail, Outlook on Windows, Outlook.com, and Yahoo ignore them
and use the fallback stack. So a custom font is never load-bearing — the fallbacks carry the
rest of the market. Set one with config.email.web_font:
Midwest.configure do |config| # Self-hosted font files (recommended — most reliable): config.email.web_font = { family: "Inter", sources: [ { url: "https://cdn.example.com/inter-400.woff2", weight: 400 }, { url: "https://cdn.example.com/inter-600.woff2", weight: 600 } ] } # — or a single file — config.email.web_font = { family: "Inter", url: "https://cdn.example.com/inter.woff2" } # — or a hosted stylesheet (Google Fonts, etc.) — config.email.web_font = { family: "Inter", import_url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" }endThe custom family leads the default stack ("Inter", -apple-system, …), so supporting
clients use it and the rest fall back. The @font-face is wrapped in @media screen —
this is the important part: an @font-face that Word-Outlook can parse makes it fall back to
Times New Roman for the entire email, so it's hidden from Outlook, which then just uses the
next font in the stack. A hosted import_url becomes an Outlook-guarded <link>.
midwest_email(web_font: { … }) overrides it per message. Code displays
(midwest_email_code) keep their monospace font regardless.
Dark mode
With config.email.dark_mode on (the default), each email carries a
prefers-color-scheme: dark block and the class hooks it needs, tuned to the same
light/dark pairings Midwest's web dark mode uses. Some clients (Outlook.com, the Gmail app)
apply their own colour inversion regardless; the palette picks colours that survive it.
The sections
| Helper | For |
|---|---|
midwest_email |
The document wrapper — head, <style>, the 600px container. Everything goes inside it. |
midwest_email_header |
Logo bar — centred, split (logo + link), or a text wordmark. |
midwest_email_hero |
Headline, subhead, CTA, optional image. |
midwest_email_content |
Prose block — heading, body, optional CTA. The workhorse. |
midwest_email_cta |
High-emphasis call-to-action band on an accent background. |
midwest_email_alert |
Status callout on a semantic state (:success/:warning/:error/:info). |
midwest_email_code |
One-time passcode / verification code, with expiry. |
midwest_email_details |
Key/value pairs — order number, date, payment. |
midwest_email_receipt |
Line items with subtotal / tax / total. |
midwest_email_order_summary |
Product rows with thumbnails. |
midwest_email_status |
Order / shipment progress — a table-drawn vertical stepper. |
midwest_email_data_table |
Generic tabular data for invoices and reports (zebra rows). |
midwest_email_stats |
2–4 metric tiles with trend deltas. |
midwest_email_address |
Shipping / billing blocks, side by side or stacked. |
midwest_email_user |
Avatar (or initials), name, role — for mentions and invites. |
midwest_email_quote |
Excerpted comment for reply notifications. |
midwest_email_attachments |
File rows with a type badge and size. |
midwest_email_social |
Row of hosted-PNG social icons. |
midwest_email_signature |
Sign-off block. |
midwest_email_footer |
Address, unsubscribe / manage-preferences, legal. |
Marketing sections:
| Helper | For |
|---|---|
midwest_email_feature_grid |
Icon + title + copy, 2–3 across. |
midwest_email_feature_row |
Alternating image/text "zigzag" rows. |
midwest_email_product_grid |
Product cards — image, title, price, CTA. |
midwest_email_article_list |
Newsletter stories — thumbnail, title, excerpt. |
midwest_email_testimonial |
Quote with an avatar attribution. |
midwest_email_logo_cloud |
Customer / partner logo row. |
midwest_email_pricing |
2–3 plan columns with feature lists. |
midwest_email_faq |
Question/answer stack. |
midwest_email_survey |
One-tap rating row; each option encodes its score in the URL. |
midwest_email_video |
Linked poster with a play button. |
midwest_email_app_badges |
App Store / Google Play badges. |
midwest_email_countdown |
Urgency band with a static deadline. |
More building blocks:
| Helper | For |
|---|---|
midwest_email_callout |
Neutral tip / note box (accent or neutral) — distinct from a state alert. |
midwest_email_button_group |
A primary + secondary button row, stackable. |
midwest_email_bullet_list |
A marked list — check, bullet, arrow, or numbered. |
midwest_email_steps |
Numbered instructional steps ("get started in 3 steps"). |
midwest_email_coupon |
A dashed-border promo box with a code and value. |
midwest_email_event |
A date-badge event card with time, location, and a CTA. |
midwest_email_progress |
A table-drawn progress bar with a label and percentage. |
midwest_email_rating |
A star rating display with an exact score and review count. |
Plus the primitives every section is built from: midwest_email_button,
midwest_email_heading, midwest_email_text, midwest_email_image,
midwest_email_link, midwest_email_divider, midwest_email_spacer,
midwest_email_section, midwest_email_columns.
Rules to know
- Every URL must be absolute. A relative
src/hrefis a broken image or dead link in every client. Setdefault_url_optionsand use*_urlroute helpers. Images and links raise on a relative URL in development and test. - Images need an
alt.midwest_email_imagerequires it (alt: ""is allowed for decorative images) — a missing alt renders as a broken-image placeholder when images are blocked, which is the default in much of Outlook and Gmail. - Body copy goes through
midwest_email_text, not raw<p>. A bare<p>gets no inline styling and falls back to the client's defaults. - No SVG, no interactivity. Email can't run JavaScript or render inline SVG. Charts and icons are hosted images; accordions and forms don't exist here by design.
Client testing
The test suite proves the markup is the shape known to work across clients — it can't prove
an email renders correctly in Outlook 2016. Before relying on a template, send it through
a service like Litmus or Email on Acid. midwest_email output is self-contained HTML, so
it drops straight into any of them.
Midwest ships an automated visual-regression suite that catches most of this at development
time (dark-mode colours, layout geometry) — see EMAIL_VRT.md. For the real-client pass,
bin/email exports every demo mailer and every section as a self-contained .html file
under tmp/emails/ (with an index), ready to upload to Litmus / Email on Acid or open
directly in a mail client — the manual step at a release cut.