进入用户的邮箱是一种特权。作为 PlanetScale,我们非常注重细节,花费了大量时间来确保邮件的质量。
我们最近推出了每周数据库报告邮件。这些邮件会定期发送,帮助用户轻松查看数据库性能、活动情况及可以采取的下一步行动。
本文将介绍我们如何使用 Ruby on Rails 和 Tailwind CSS 构建每周数据库报告邮件的 HTML 邮件,并分享解决过程中遇到的挑战。


注意事项

本文并非完整的操作教程,而是一份参考指南,适合探索如何在你的应用程序中使用 Rails 和 Tailwind 构建 HTML 邮件的开发者。


设置 Rails 应用

如果你希望轻松跟随本文的内容,请确保你已经搭建好一个 Rails 应用,并将其设置为支持 PostCSS。我们建议使用 Rails 7+,同时安装 cssbundling-rails。
创建新的 Rails 应用:

rails new myapp --css postcss

更新现有 Rails 应用

如果你已经有一个 Rails 应用,但尚未配置 PostCSS,可以按照以下步骤完成设置。如果你刚刚创建了新的应用,则可以跳过此步骤。
添加 cssbundling-rails

./bin/bundle add cssbundling-rails

执行以下命令以生成一个基础设置:

./bin/rails css:install:postcss

如果系统提示覆盖现有文件,而你已有 postcss.config.js 文件,可以选择“不覆盖”(输入 n)。
另外,如果在 app/assets/stylesheets/ 目录下已有 application.css 文件,可以通过以下命令还原这些文件:

git restore --staged app/assets/stylesheets/application.css
git checkout app/assets/stylesheets/application.css

删除生成的 application.postcss.css 文件:

rm app/assets/stylesheets/application.postcss.css

如果目录中已有 application.postcss.css 文件,选择“不覆盖”(输入 n)。设置完成后,我们就可以配置 PostCSS。


配置用于邮件的 PostCSS

我们需要单独设置两个 CSS 文件:一个用于 Web 应用,另一个用于邮件。为什么这样做?因为邮件客户端对 CSS 的支持非常有限,与 Web 的 CSS 不完全相同。我们现在来创建邮件专用 CSS 文件:
创建邮件 CSS 文件:

touch app/assets/stylesheets/mailer.postcss.css

每个 CSS 文件将使用不同的 Tailwind 配置,以优化其使用场景。通过 PostCSS CLI,可以轻松生成这些分离的文件。
修改 package.json

{
  "scripts": {
    "build:css": "postcss ./app/assets/stylesheets/{application,mailer}.postcss.css --base ./app/assets/stylesheets --dir ./app/assets/builds"
  }
}

接下来,编译 CSS 文件:

yarn build:css

验证生成的 CSS 文件是否存在:

ls app/assets/stylesheets/

生成的文件实际上只是经过 PostCSS 处理后的纯 CSS 文件。可以通过以下命令删除 .postcss 扩展名:

mv app/assets/stylesheets/application{.postcss,}.css
mv app/assets/stylesheets/mailer{.postcss,}.css

重新编译 CSS:

yarn build:css

验证更新结果:

ls app/assets/builds/

创建邮件专用的样式表

设置 Tailwind 并配置 PostCSS,这将允许我们分别优化针对 Web 和邮件的输出。

安装 Tailwind CSS

yarn add tailwindcss postcss autoprefixer postcss-import --dev

初始化 Tailwind CSS:

yarn tailwindcss init

更新 PostCSS 配置文件:

// postcss.config.js
module.exports = (api) => {
  if (/mailer/.test(api.file.basename)) {
    return {
      plugins: {
        'postcss-import': {},
        'postcss-custom-properties': {
          preserve: false
        },
        'tailwindcss/nesting': {},
        tailwindcss: {
          config: './tailwind.config.mailer.js'
        }
      }
    }
  }

  return {
    plugins: {
      'postcss-import': {},
      'tailwindcss/nesting': {},
      tailwindcss: {},
      autoprefixer: {}
    }
  }
}

配置邮件专用的 Tailwind 文件

编辑 tailwind.config.mailer.js 文件:

module.exports = {
  content: ['app/helpers/mailer_helper.rb', 'app/views/*_mailer/*.html.erb', 'app/views/layouts/mailer.html.erb'],
  future: {
    disableColorOpacityUtilitiesByDefault: true
  },
  theme: {
    extend: {
      borderRadius: {
        none: '0',
        xs: '2px',
        sm: '4px',
        DEFAULT: '6px',
        md: '8px',
        lg: '10px',
        full: '9999px'
      },
      fontSize: {
        xs: '10px',
        sm: '12px',
        base: '14px',
        lg: '16px',
        xl: '18px',
        '2xl': '22px',
        '3xl': '24px',
        '4xl': '28px',
        '5xl': '32px'
      },
      spacing: {
        0.5: '4px',
        1: '8px',
        1.5: '12px',
        2: '16px',
        2.5: '20px',
        3: '24px',
        4: '32px',
        4.5: '36px',
        5: '40px',
        6: '48px',
        7: '56px',
        8: '64px',
        9: '72px',
        10: '80px'
      }
    }
  }
}

内联 CSS 样式的重要性

邮件客户端对样式文件的支持普遍较差。最简单的解决方法是将样式内联到 HTML 中,但这种方式容易出错且难以维护,因为你无法使用 CSS 类或在 HTML 中重用样式。
为了解决这一问题,我们使用了一个叫 roadie 的库,它能够自动将外部样式转换为内联样式,并且与 Tailwind CSS 兼容。

安装 Roadie Rails

./bin/bundle add roadie-rails

配置 Roadie Rails

编辑 app/mailers/application_mailer.rb 文件,将以下内容添加进去:

class ApplicationMailer < ActionMailer::Base
  include Roadie::Rails::Automatic

  default from: "from@example.com"
  layout "mailer"
end

设置邮件布局

邮件布局中的主要容器是一个包裹内容的外层框架。通常,在邮件中这是一个约 600px 宽的居中栏,能够在更小的屏幕上自动缩小并居中显示。
以下是一个布局示例:

<!DOCTYPE html>
<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title><%= message.subject %> | PlanetScale</title>

    <%= stylesheet_link_tag "mailer" %>
  </head>

  <body>
    <div role="article" aria-roledescription="email" aria-label="<%= message.subject %>" lang="en">
      <table border="0" cellpadding="0" cellspacing="0" class="font-sans" width="100%">
        <tr>
          <td height="32"></td>
        </tr>

        <tr>
          <td align="center">
            <table border="0" cellpadding="0" cellspacing="0" class="w-full max-w-[684px] px-0 sm:w-[684px] sm-px-2" width="100%">
              <tr>
                <td>
                  <%= yield %>
                </td>
              </tr>
            </table>
          </td>
        </tr>

        <tr>
          <td height="32"></td>
        </tr>
      </table>
    </div>
  </body>
</html>

支持深色模式(Dark Mode)

许多用户更喜欢深色模式,因此支持它非常重要。Tailwind CSS 让我们轻松实现了这一功能。
<head> 部分添加以下元标签:

<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark only">

然后在 CSS 文件中定义样式:

/* app/assets/stylesheets/mailer.css */
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

:root {
  color-scheme: light dark;
  supported-color-schemes: light dark;
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #111 !important;
    color: #fafafa !important;
  }

  a {
    color: #47b7f8 !important;
  }
}

添加邮件预览文字(Preheader)

预览文字是一种鼓励用户打开邮件的好方法。它通常是邮件主题旁边的文本内容。以下是设置预览文字的方法:
/app/views/database_mailer/database_weekly_report.html.erb 文件中添加:

<% content_for :preheader do %>
  <%= @report.period_start_label(full: false) %> – <%= @report.period_end_label(full: false) %> Here’s a look at the performance of your <%= @database.display_name %> database.
<% end %>

然后在布局文件 /app/views/layouts/mailer.html.erb 中:

<!DOCTYPE html>
<html>
  <body>
    <% if content_for?(:preheader) %>
    <div class="hidden">
      <%= yield :preheader %>
    </div>
    <% end %>
  </body>
</html>

禁止 Apple 自动链接

在 iPhone 或 iPad 中查看邮件时,电话号码、地址、日期甚至一些特定词(例如“今晚”)经常被自动转化为蓝色超链接。这些链接会触发应用事件,例如拨号或创建日历事件。这在某些场景中可能很方便,但对于许多设计和品牌来说,这恰恰是个干扰。
以下是关闭自动链接的方法:

<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
<meta name="x-apple-disable-message-reformatting">

解决 Gmail 内容截断问题

Gmail 在邮件内容超过 102KB 时,会截断并用“查看完整内容”链接显示剩余部分。这对用户体验而言是不理想的。

剔除不必要的内容

建议删除所有不必要的内容。例如,我们限制显示的慢查询记录数量为 10 条:

<% @report.slow_queries.first(10).each do |query| %>
  <tr>
    <td class="font-mono text-sm text-primary">
      <%= truncate(query.sql, length: 200) %>
    </td>
  </tr>

此外,可以使用链接转向完整内容:

<% @report.slow_queries.first(10).each do |query| %>
  <tr>
    <td class="font-mono text-sm text-primary">
      <%= truncate(query.sql, length: 200) %>
    </td>
    <td align="right">
      <%= link_to "View full query", query %>
    </td>
  </tr>

减少文件大小

即便裁剪内容后,邮件有时仍会被截断。我们可以通过优化 Tailwind 的导入来进一步减少文件大小:

/* 替换部分导入 */
@import 'tailwindcss/utilities';

@import 'mailer/base';
@import 'mailer/theme';

我们删除了 Tailwind 的预检样式和组件工具,靠自定义样式减少总体文件大小:

html {
  line-height: 1.5;
}

body {
  line-height: inherit;
  margin: 0;
}

img {
  border-style: none;
  display: block;
  vertical-align: middle;
  max-width: 100%;
  height: auto;
}

测试结果显示,我们的邮件大小从 80KB 减少到 45KB,成功解决 Gmail 内容截断问题。


Gmail 桌面样式优化

尽管我们设计了响应式样式,但在 Gmail 桌面版中,邮件的移动端样式始终被应用。这是因为 Gmail 不支持某些 CSS 转义字符。

解决方法:定义辅助工具类

由于 Gmail 不支持特殊符号的转义输出,建议定义自定义工具类来处理样式问题:

@media (min-width: 640px) {
  .sm-block {
    display: block !important;
  }

  .sm-hidden {
    display: none !important;
  }

  .sm-inline {
    display: inline !important;
  }
}

然后更新代码:

<span class="hidden sm-inline">显示在桌面端</span>

测试邮件内容

邮件预览

Rails 提供一个简单的方式在浏览器中预览邮件样式。在 test/mailers/previews/database_mailer_preview.rb 文件中创建邮件预览类:

class DatabaseMailerPreview < ActionMailer::Preview
  def database_weekly_report
    DatabaseMailer.with(
      database: database,
      recipient: user,
      report: report,
      subscription: subscription,
    )
  end

  private

  def database
    Database.first!
  end

  def recipient
    User.first!
  end

  def report
    DatabaseReport.new(
      database: database,
      period_start: Time.current.beginning_of_week,
      period_end: Time.current.end_of_week,
    )
  end

  def subscription
    Subscription.new(
      database: database,
      user: user,
    )
  end
end

预览 URL 为 http://localhost:3000/rails/mailers/database_mailer/database_weekly_report


发送测试邮件

为了在真实邮件客户端中测试样式和功能,可以创建一个 Rake 任务,用于发送测试邮件,代码如下:

namespace :dev do
  namespace :mailer do
    task :database_weekly_report, [:email] => :environment do |_t, args|
      raise ArgumentError, "Rails environment is not development" unless Rails.env.development?
      raise ArgumentError, "Email argument is missing" unless args[:email]

      mailer = DatabaseMailerPreview.new.database_weekly_report
      mailer.to = args[:email]
      mailer.deliver
    end
  end
end

通过以下命令发送测试邮件:

bin/rails dev:mailer:database_weekly_report[info@planetscale.com]

总结

尽管构建 HTML 邮件较为复杂,但 Ruby on Rails 和 Tailwind CSS 让这个过程变得更为顺畅。它们不仅加快了开发过程,还简化了测试工作。如果你对实现细节有任何疑问,请随时联系!



Rails 和 Tailwind CSS 实现 HTML 邮件:完整指南插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://folen.top/2025/09/13/rails-tailwind-css-html/