rails-mailers

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Email with ActionMailer

使用ActionMailer发送邮件

Send transactional and notification emails using ActionMailer, integrated with SolidQueue for async delivery. Create HTML and text templates, preview emails in development, and test thoroughly.
<when-to-use> - Sending transactional emails (password resets, confirmations, receipts) - Sending notification emails (updates, alerts, digests) - Delivering emails asynchronously via background jobs - Creating email templates with HTML and text versions - Testing email delivery and content </when-to-use> <benefits> - **Async Delivery** - ActionMailer integrates with SolidQueue for non-blocking email sending - **Template Support** - ERB templates for HTML and text email versions - **Preview in Development** - See emails without sending via /rails/mailers - **Testing Support** - Full test suite for delivery and content - **Layouts** - Shared layouts for consistent email branding - **Attachments** - Send files (PDFs, images) with emails </benefits> <verification-checklist> Before completing mailer work: - ✅ Async delivery used (deliver_later, not deliver_now) - ✅ Both HTML and text templates provided - ✅ URL helpers used (not path helpers) - ✅ Email previews created for development - ✅ Mailer tests passing (delivery and content) - ✅ SolidQueue configured for background delivery </verification-checklist> <standards> - ALWAYS deliver emails asynchronously with deliver_later (NOT deliver_now) - Provide both HTML and text email templates - Use *_url helpers (NOT *_path) for links in emails - Set default 'from' address in ApplicationMailer - Create email previews for development (/rails/mailers) - Configure default_url_options for each environment - Use inline CSS for email styling (email clients strip external styles) - Test email delivery and content - Use parameterized mailers (.with()) for cleaner syntax </standards>
使用ActionMailer发送事务性和通知类邮件,集成SolidQueue实现异步投递。创建HTML和文本模板,在开发环境中预览邮件,并进行全面测试。
<when-to-use> - 发送事务性邮件(密码重置、确认通知、收据) - 发送通知类邮件(更新、告警、摘要) - 通过后台任务异步投递邮件 - 创建HTML和文本版本的邮件模板 - 测试邮件投递和内容 </when-to-use> <benefits> - **异步投递** - ActionMailer集成SolidQueue实现非阻塞邮件发送 - **模板支持** - 支持HTML和文本邮件版本的ERB模板 - **开发环境预览** - 通过/rails/mailer路径无需发送即可查看邮件 - **测试支持** - 完整的投递和内容测试套件 - **布局** - 共享布局实现统一的邮件品牌风格 - **附件** - 支持随邮件发送文件(PDF、图片) </benefits> <verification-checklist> 完成邮件开发工作前: - ✅ 使用异步投递(deliver_later,而非deliver_now) - ✅ 同时提供HTML和文本模板 - ✅ 使用URL助手(而非路径助手) - ✅ 为开发环境创建邮件预览 - ✅ 邮件测试通过(投递和内容) - ✅ 配置SolidQueue实现后台投递 </verification-checklist> <standards> - 始终使用deliver_later异步投递邮件(不要使用deliver_now) - 同时提供HTML和文本邮件模板 - 邮件中的链接使用*_url助手(不要使用*_path) - 在ApplicationMailer中设置默认发件人地址 - 为开发环境创建邮件预览(/rails/mailers) - 为每个环境配置default_url_options - 使用内联CSS设置邮件样式(邮件客户端会剥离外部样式) - 测试邮件投递和内容 - 使用参数化邮件器(.with())获得更简洁的语法 </standards>

ActionMailer Setup

ActionMailer配置

<pattern name="actionmailer-basic-setup"> <description>Configure ActionMailer for email delivery</description>
Mailer Class:
ruby
undefined
<pattern name="actionmailer-basic-setup"> <description>配置ActionMailer用于邮件投递</description>
**邮件类:
ruby
undefined

app/mailers/application_mailer.rb

app/mailers/application_mailer.rb

class ApplicationMailer < ActionMailer::Base default from: "noreply@example.com" layout "mailer" end
class ApplicationMailer < ActionMailer::Base default from: "noreply@example.com" layout "mailer" end

app/mailers/notification_mailer.rb

app/mailers/notification_mailer.rb

class NotificationMailer < ApplicationMailer def welcome_email(user) @user = user @login_url = login_url mail(to: user.email, subject: "Welcome to Our App") end
def password_reset(user) @user = user @reset_url = password_reset_url(user.reset_token) mail(to: user.email, subject: "Password Reset Instructions") end end

**HTML Template:**

```erb
<%# app/views/notification_mailer/welcome_email.html.erb %>
<h1>Welcome, <%= @user.name %>!</h1>
<p>Thanks for signing up. Get started by logging in:</p>
<%= link_to "Login Now", @login_url, class: "button" %>
Text Template:
erb
<%# app/views/notification_mailer/welcome_email.text.erb %>
Welcome, <%= @user.name %>!

Thanks for signing up. Get started by logging in:
<%= @login_url %>
Usage (Async with SolidQueue):
ruby
undefined
class NotificationMailer < ApplicationMailer def welcome_email(user) @user = user @login_url = login_url mail(to: user.email, subject: "Welcome to Our App") end
def password_reset(user) @user = user @reset_url = password_reset_url(user.reset_token) mail(to: user.email, subject: "Password Reset Instructions") end end

**HTML模板:**

```erb
<%# app/views/notification_mailer/welcome_email.html.erb %>
<h1>Welcome, <%= @user.name %>!</h1>
<p>Thanks for signing up. Get started by logging in:</p>
<%= link_to "Login Now", @login_url, class: "button" %>
文本模板:
erb
<%# app/views/notification_mailer/welcome_email.text.erb %>
Welcome, <%= @user.name %>!

Thanks for signing up. Get started by logging in:
<%= @login_url %>
使用方式(通过SolidQueue异步):
ruby
undefined

In controller or service

在控制器或者服务中

NotificationMailer.welcome_email(@user).deliver_later NotificationMailer.password_reset(@user).deliver_later(queue: :mailers)

**Why:** ActionMailer integrates seamlessly with SolidQueue for async delivery. Always use deliver_later to avoid blocking requests. Provide both HTML and text versions for compatibility.
</pattern>

<antipattern>
<description>Using deliver_now in production (blocks HTTP request)</description>
<bad-example>

```ruby
NotificationMailer.welcome_email(@user).deliver_later NotificationMailer.password_reset(@user).deliver_later(queue: :mailers)

**原因:** ActionMailer可以无缝集成SolidQueue实现异步投递。始终使用deliver_later避免阻塞请求。同时提供HTML和文本版本保证兼容性。
</pattern>

<antipattern>
<description>在生产环境使用deliver_now(会阻塞HTTP请求)</description>
<bad-example>

```ruby

❌ WRONG - Blocks HTTP request thread

❌ 错误 - 会阻塞HTTP请求线程

def create @user = User.create!(user_params) NotificationMailer.welcome_email(@user).deliver_now # Blocks! redirect_to @user end

</bad-example>
<good-example>

```ruby
def create @user = User.create!(user_params) NotificationMailer.welcome_email(@user).deliver_now # 阻塞! redirect_to @user end

</bad-example>
<good-example>

```ruby

✅ CORRECT - Async delivery via SolidQueue

✅ 正确 - 通过SolidQueue异步投递

def create @user = User.create!(user_params) NotificationMailer.welcome_email(@user).deliver_later # Non-blocking redirect_to @user end

</good-example>

**Why bad:** deliver_now blocks the HTTP request until SMTP completes, creating slow response times and poor user experience. deliver_later uses SolidQueue to send email in background.
</antipattern>

<pattern name="parameterized-mailers">
<description>Use .with() to pass parameters cleanly to mailers</description>

```ruby
class NotificationMailer < ApplicationMailer
  def custom_notification
    @user = params[:user]
    @message = params[:message]
    mail(to: @user.email, subject: params[:subject])
  end
end
def create @user = User.create!(user_params) NotificationMailer.welcome_email(@user).deliver_later # 非阻塞 redirect_to @user end

</good-example>

**错误原因:** deliver_now会阻塞HTTP请求直到SMTP执行完成,导致响应时间变长,用户体验差。deliver_later使用SolidQueue在后台发送邮件。
</antipattern>

<pattern name="parameterized-mailers">
<description>使用.with()向邮件器清晰传递参数</description>

```ruby
class NotificationMailer < ApplicationMailer
  def custom_notification
    @user = params[:user]
    @message = params[:message]
    mail(to: @user.email, subject: params[:subject])
  end
end

Usage

使用方式

NotificationMailer.with( user: user, message: "Update available", subject: "System Alert" ).custom_notification.deliver_later

**Why:** Cleaner syntax, easier to read and modify, and works seamlessly with background jobs.
</pattern>

---
NotificationMailer.with( user: user, message: "Update available", subject: "System Alert" ).custom_notification.deliver_later

**原因:** 语法更简洁,更容易阅读和修改,并且可以和后台任务无缝兼容。
</pattern>

---

Email Templates

邮件模板

<pattern name="email-layouts"> <description>Shared layouts for consistent email branding</description>
HTML Layout:
erb
<%# app/views/layouts/mailer.html.erb %>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      body {
        font-family: Arial, sans-serif;
        max-width: 600px;
        margin: 0 auto;
        color: #333;
      }
      .header {
        background-color: #4F46E5;
        color: white;
        padding: 20px;
        text-align: center;
      }
      .content {
        padding: 20px;
      }
      .button {
        display: inline-block;
        padding: 12px 24px;
        background-color: #4F46E5;
        color: white;
        text-decoration: none;
        border-radius: 4px;
      }
      .footer {
        padding: 20px;
        text-align: center;
        font-size: 12px;
        color: #666;
      }
    </style>
  </head>
  <body>
    <div class="header">
      <h1>Your App</h1>
    </div>
    <div class="content">
      <%= yield %>
    </div>
    <div class="footer">
      <p>&copy; 2025 Your Company. All rights reserved.</p>
    </div>
  </body>
</html>
Text Layout:
erb
<%# app/views/layouts/mailer.text.erb %>
================================================================================
YOUR APP
================================================================================

<%= yield %>

--------------------------------------------------------------------------------
© 2025 Your Company. All rights reserved.
Why: Consistent branding across all emails. Inline CSS ensures styling works across email clients. </pattern>
<pattern name="email-attachments"> <description>Attach files to emails (PDFs, CSVs, images)</description>
ruby
class ReportMailer < ApplicationMailer
  def monthly_report(user, data)
    @user = user

    # Regular attachment
    attachments["report.pdf"] = {
      mime_type: "application/pdf",
      content: generate_pdf(data)
    }

    # Inline attachment (for embedding in email body)
    attachments.inline["logo.png"] = File.read(
      Rails.root.join("app/assets/images/logo.png")
    )

    mail(to: user.email, subject: "Monthly Report")
  end
end
In template:
erb
<%# Reference inline attachment %>
<%= image_tag attachments["logo.png"].url %>
Why: Attach reports, exports, or inline images. Inline attachments can be referenced in email body with image_tag. </pattern>
<antipattern> <description>Using *_path helpers instead of *_url in emails (broken links)</description> <bad-example>
ruby
undefined
<pattern name="email-layouts"> <description>共享布局实现统一的邮件品牌风格</description>
HTML布局:
erb
<%# app/views/layouts/mailer.html.erb %>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      body {
        font-family: Arial, sans-serif;
        max-width: 600px;
        margin: 0 auto;
        color: #333;
      }
      .header {
        background-color: #4F46E5;
        color: white;
        padding: 20px;
        
      }
      .content {
        padding: 20px;
      }
      .button {
        display: inline-block;
        padding: 12px 24px;
        background-color: #4F46E5;
        color: white;
        text-decoration: none;
        border-radius: 4px;
      }
      .footer {
        padding: 20px;
        text-align: center;
        font-size: 12px;
        color: #666;
      }
    </style>
  </head>
  <body>
    <div class="header">
      <h1>Your App</h1>
    </div>
    <div class="content">
      <%= yield %>
    </div>
    <div class="footer">
      <p>&copy; 2025 Your Company. All rights reserved.</p>
    </div>
  </body>
</html>
文本布局:
erb
<%# app/views/layouts/mailer.text.erb %>
================================================================================
YOUR APP
================================================================================

<%= yield %>

--------------------------------------------------------------------------------
© 2025 Your Company. All rights reserved.
原因: 所有邮件保持一致的品牌风格。内联CSS确保样式在所有邮件客户端都能正常显示。 </pattern>
<pattern name="email-attachments"> <description>在邮件中附加文件(PDF、CSV、图片)</description>
ruby
class ReportMailer < ApplicationMailer
  def monthly_report(user, data)
    @user = user

    # 普通附件
    attachments["report.pdf"] = {
      mime_type: "application/pdf",
      content: generate_pdf(data)
    }

    # 内联附件(用于嵌入邮件正文)
    attachments.inline["logo.png"] = File.read(
      Rails.root.join("app/assets/images/logo.png")
    )

    mail(to: user.email, subject: "Monthly Report")
  end
end
**在模板中:
erb
<%# 引用内联附件 %>
<%= image_tag attachments["logo.png"].url %>
原因: 附加报告、导出文件或者内联图片。内联附件可以通过image_tag在邮件正文中引用。 </pattern>
<antipattern> <description>在邮件中使用*_path助手而非*_url(链接失效)</description> <bad-example>
ruby
undefined

❌ WRONG - Relative path doesn't work in emails

❌ 错误 - 相对路径在邮件中无法使用

def welcome_email(user) @user = user @login_url = login_path # => "/login" (relative path) mail(to: user.email, subject: "Welcome") end

</bad-example>
<good-example>

```ruby
def welcome_email(user) @user = user @login_url = login_path # => "/login" (相对路径) mail(to: user.email, subject: "Welcome") end

</bad-example>
<good-example>

```ruby

✅ CORRECT - Full URL works in emails

✅ 正确 - 完整URL在邮件中可以正常使用

def welcome_email(user) @user = user @login_url = login_url # => "https://example.com/login" (absolute URL) mail(to: user.email, subject: "Welcome") end
def welcome_email(user) @user = user @login_url = login_url # => "https://example.com/login" (绝对URL) mail(to: user.email, subject: "Welcome") end

Required configuration

必需配置

config/environments/production.rb

config/environments/production.rb

config.action_mailer.default_url_options = { host: "example.com", protocol: "https" }

</good-example>

**Why bad:** Emails are viewed outside your application context, so relative paths don't work. Always use *_url helpers to generate absolute URLs.
</antipattern>

---
config.action_mailer.default_url_options = { host: "example.com", protocol: "https" }

</good-example>

**错误原因:** 邮件是在你的应用上下文之外查看的,所以相对路径无法工作。始终使用*_url助手生成绝对URL。
</antipattern>

---

Email Testing

邮件测试

<pattern name="letter-opener-setup"> <description>Preview emails in browser during development without sending</description>
Configuration:
ruby
undefined
<pattern name="letter-opener-setup"> <description>开发过程中在浏览器中预览邮件,无需实际发送</description>
**配置:
ruby
undefined

Gemfile

Gemfile

group :development do gem "letter_opener" end
group :development do gem "letter_opener" end

config/environments/development.rb

config/environments/development.rb

config.action_mailer.delivery_method = :letter_opener config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
config.action_mailer.delivery_method = :letter_opener config.action_mailer.default_url_options = { host: "localhost", port: 3000 }

config/environments/production.rb

config/environments/production.rb

config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: "smtp.sendgrid.net", port: 587, user_name: Rails.application.credentials.dig(:smtp, :username), password: Rails.application.credentials.dig(:smtp, :password), authentication: :plain, enable_starttls_auto: true } config.action_mailer.default_url_options = { host: "example.com", protocol: "https" }

**Why:** letter_opener opens emails in browser during development - no SMTP setup needed. Test email appearance without actually sending.
</pattern>

<pattern name="mailer-previews">
<description>Preview all email variations at /rails/mailers</description>

```ruby
config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: "smtp.sendgrid.net", port: 587, user_name: Rails.application.credentials.dig(:smtp, :username), password: Rails.application.credentials.dig(:smtp, :password), authentication: :plain, enable_starttls_auto: true } config.action_mailer.default_url_options = { host: "example.com", protocol: "https" }

**原因:** 开发过程中letter_opener会在浏览器中打开邮件 - 无需配置SMTP。无需实际发送即可测试邮件外观。
</pattern>

<pattern name="mailer-previews">
<description>在/rails/mailers路径预览所有邮件变体</description>

```ruby

test/mailers/previews/notification_mailer_preview.rb

test/mailers/previews/notification_mailer_preview.rb

class NotificationMailerPreview < ActionMailer::Preview

Preview at http://localhost:3000/rails/mailers/notification_mailer/welcome_email

def welcome_email user = User.first || User.new(name: "Test User", email: "test@example.com") NotificationMailer.welcome_email(user) end
def password_reset user = User.first || User.new(name: "Test User", email: "test@example.com") user.reset_token = "sample_token_123" NotificationMailer.password_reset(user) end

Preview with different data

def welcome_email_long_name user = User.new(name: "Christopher Alexander Montgomery III", email: "long@example.com") NotificationMailer.welcome_email(user) end end

**Why:** Mailer previews at /rails/mailers let you see all email variations without sending. Test different edge cases (long names, missing data, etc.).
</pattern>

<pattern name="mailer-testing">
<description>Test email delivery and content with ActionMailer::TestCase</description>

```ruby
class NotificationMailerPreview < ActionMailer::Preview

预览地址:http://localhost:3000/rails/mailers/notification_mailer/welcome_email

def welcome_email user = User.first || User.new(name: "Test User", email: "test@example.com") NotificationMailer.welcome_email(user) end
def password_reset user = User.first || User.new(name: "Test User", email: "test@example.com") user.reset_token = "sample_token_123" NotificationMailer.password_reset(user) end

使用不同数据预览

def welcome_email_long_name user = User.new(name: "Christopher Alexander Montgomery III", email: "long@example.com") NotificationMailer.welcome_email(user) end end

**原因:** /rails/mailers路径下的邮件预览让你无需发送即可查看所有邮件变体。测试不同的边缘场景(长名字、缺失数据等)。
</pattern>

<pattern name="mailer-testing">
<description>使用ActionMailer::TestCase测试邮件投递和内容</description>

```ruby

test/mailers/notification_mailer_test.rb

test/mailers/notification_mailer_test.rb

class NotificationMailerTest < ActionMailer::TestCase test "welcome_email sends with correct attributes" do user = users(:alice) email = NotificationMailer.welcome_email(user)
# Test delivery
assert_emails 1 do
  email.deliver_now
end

# Test attributes
assert_equal [user.email], email.to
assert_equal ["noreply@example.com"], email.from
assert_equal "Welcome to Our App", email.subject

# Test content
assert_includes email.html_part.body.to_s, user.name
assert_includes email.text_part.body.to_s, user.name
assert_includes email.html_part.body.to_s, "Login Now"
end
test "delivers via background job" do user = users(:alice)
assert_enqueued_with(job: ActionMailer::MailDeliveryJob, queue: "mailers") do
  NotificationMailer.welcome_email(user).deliver_later(queue: :mailers)
end
end
test "password_reset includes reset link" do user = users(:alice) user.update!(reset_token: "test_token_123") email = NotificationMailer.password_reset(user)
assert_includes email.html_part.body.to_s, "test_token_123"
assert_includes email.html_part.body.to_s, "password_reset"
end end

**Why:** Test email delivery, content, and background job enqueuing. Verify recipients, subjects, and that emails are queued properly.
</pattern>

---
class NotificationMailerTest < ActionMailer::TestCase test "welcome_email sends with correct attributes" do user = users(:alice) email = NotificationMailer.welcome_email(user)
# 测试投递
assert_emails 1 do
  email.deliver_now
end

# 测试属性
assert_equal [user.email], email.to
assert_equal ["noreply@example.com"], email.from
assert_equal "Welcome to Our App", email.subject

# 测试内容
assert_includes email.html_part.body.to_s, user.name
assert_includes email.text_part.body.to_s, user.name
assert_includes email.html_part.body.to_s, "Login Now"
end
test "delivers via background job" do user = users(:alice)
assert_enqueued_with(job: ActionMailer::MailDeliveryJob, queue: "mailers") do
  NotificationMailer.welcome_email(user).deliver_later(queue: :mailers)
end
end
test "password_reset includes reset link" do user = users(:alice) user.update!(reset_token: "test_token_123") email = NotificationMailer.password_reset(user)
assert_includes email.html_part.body.to_s, "test_token_123"
assert_includes email.html_part.body.to_s, "password_reset"
end end

**原因:** 测试邮件投递、内容和后台任务入队。验证收件人、主题,以及邮件是否正确入队。
</pattern>

---

Email Configuration

邮件配置

<pattern name="environment-configuration"> <description>Configure ActionMailer for each environment</description>
Development:
ruby
undefined
<pattern name="environment-configuration"> <description>为每个环境配置ActionMailer</description>
**开发环境:
ruby
undefined

config/environments/development.rb

config/environments/development.rb

config.action_mailer.delivery_method = :letter_opener config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true config.action_mailer.default_url_options = { host: "localhost", port: 3000 }

**Test:**

```ruby
config.action_mailer.delivery_method = :letter_opener config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true config.action_mailer.default_url_options = { host: "localhost", port: 3000 }

**测试环境:

```ruby

config/environments/test.rb

config/environments/test.rb

config.action_mailer.delivery_method = :test config.action_mailer.default_url_options = { host: "example.com" }

**Production:**

```ruby
config.action_mailer.delivery_method = :test config.action_mailer.default_url_options = { host: "example.com" }

**生产环境:**

```ruby

config/environments/production.rb

config/environments/production.rb

config.action_mailer.delivery_method = :smtp config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = false config.action_mailer.default_url_options = { host: ENV["APP_HOST"], protocol: "https" }
config.action_mailer.smtp_settings = { address: ENV["SMTP_ADDRESS"], port: ENV["SMTP_PORT"], user_name: Rails.application.credentials.dig(:smtp, :username), password: Rails.application.credentials.dig(:smtp, :password), authentication: :plain, enable_starttls_auto: true }

**Why:** Different configurations per environment. Development previews in browser, test stores emails in memory, production sends via SMTP.
</pattern>

---

<testing>

```ruby
config.action_mailer.delivery_method = :smtp config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = false config.action_mailer.default_url_options = { host: ENV["APP_HOST"], protocol: "https" }
config.action_mailer.smtp_settings = { address: ENV["SMTP_ADDRESS"], port: ENV["SMTP_PORT"], user_name: Rails.application.credentials.dig(:smtp, :username), password: Rails.application.credentials.dig(:smtp, :password), authentication: :plain, enable_starttls_auto: true }

**原因:** 每个环境使用不同的配置。开发环境在浏览器中预览,测试环境将邮件存储在内存中,生产环境通过SMTP发送。
</pattern>

---

<testing>

```ruby

test/mailers/notification_mailer_test.rb

test/mailers/notification_mailer_test.rb

class NotificationMailerTest < ActionMailer::TestCase setup do @user = users(:alice) end
test "welcome_email" do email = NotificationMailer.welcome_email(@user)
assert_emails 1 { email.deliver_now }
assert_equal [@user.email], email.to
assert_equal ["noreply@example.com"], email.from
assert_match @user.name, email.html_part.body.to_s
assert_match @user.name, email.text_part.body.to_s
end
test "enqueues for async delivery" do assert_enqueued_with(job: ActionMailer::MailDeliveryJob) do NotificationMailer.welcome_email(@user).deliver_later end end
test "uses correct queue" do assert_enqueued_with(job: ActionMailer::MailDeliveryJob, queue: "mailers") do NotificationMailer.welcome_email(@user).deliver_later(queue: :mailers) end end end
class NotificationMailerTest < ActionMailer::TestCase setup do @user = users(:alice) end
test "welcome_email" do email = NotificationMailer.welcome_email(@user)
assert_emails 1 { email.deliver_now }
assert_equal [@user.email], email.to
assert_equal ["noreply@example.com"], email.from
assert_match @user.name, email.html_part.body.to_s
assert_match @user.name, email.text_part.body.to_s
end
test "enqueues for async delivery" do assert_enqueued_with(job: ActionMailer::MailDeliveryJob) do NotificationMailer.welcome_email(@user).deliver_later end end
test "uses correct queue" do assert_enqueued_with(job: ActionMailer::MailDeliveryJob, queue: "mailers") do NotificationMailer.welcome_email(@user).deliver_later(queue: :mailers) end end end

test/system/email_delivery_test.rb

test/system/email_delivery_test.rb

class EmailDeliveryTest < ApplicationSystemTestCase test "sends welcome email after signup" do visit signup_path fill_in "Email", with: "new@example.com" fill_in "Password", with: "password" click_button "Sign Up"
assert_enqueued_emails 1
perform_enqueued_jobs

email = ActionMailer::Base.deliveries.last
assert_equal ["new@example.com"], email.to
assert_match "Welcome", email.subject
end end

</testing>

---

<related-skills>
- rails-ai:jobs - Background job processing with SolidQueue
- rails-ai:views - Email templates and layouts
- rails-ai:testing - Testing email delivery
- rails-ai:project-setup - Environment-specific email configuration
</related-skills>

<resources>

**Official Documentation:**
- [Rails Guides - Action Mailer Basics](https://guides.rubyonrails.org/action_mailer_basics.html)

**Gems & Libraries:**
- [letter_opener](https://github.com/ryanb/letter_opener) - Preview emails in browser during development

**Tools:**
- [Email on Acid](https://www.emailonacid.com/) - Email testing across clients

**Email Service Providers:**
- [SendGrid Rails Guide](https://docs.sendgrid.com/for-developers/sending-email/rubyonrails)

</resources>
class EmailDeliveryTest < ApplicationSystemTestCase test "sends welcome email after signup" do visit signup_path fill_in "Email", with: "new@example.com" fill_in "Password", with: "password" click_button "Sign Up"
assert_enqueued_emails 1
perform_enqueued_jobs

email = ActionMailer::Base.deliveries.last
assert_equal ["new@example.com"], email.to
assert_match "Welcome", email.subject
end end

</testing>

---

<related-skills>
- rails-ai:jobs - 基于SolidQueue的后台任务处理
- rails-ai:views - 邮件模板和布局
- rails-ai:testing - 测试邮件投递
- rails-ai:project-setup - 特定环境的邮件配置
</related-skills>

<resources>

**官方文档:
- [Rails Guides - Action Mailer Basics](https://guides.rubyonrails.org/action_mailer_basics.html)

**Gem和库:**
- [letter_opener](https://github.com/ryanb/letter_opener) - 开发过程中在浏览器中预览邮件

**工具:**
- [Email on Acid](https://www.emailonacid.com/) - 跨客户端邮件测试

**邮件服务提供商:**
- [SendGrid Rails Guide](https://docs.sendgrid.com/for-developers/sending-email/rubyonrails)

</resources>