Rails使用aws-sdk-ses对接AWS SES时如何解决邮件预览失效问题
问题描述
我在开发Ruby on Rails应用时,选用AWS SES服务发送用户邮件,对接使用aws-sdk-ses gem,当前本地邮件预览功能无法正常使用。
相关代码
app/mailers/user_mailer.rb 实现如下:
# frozen_string_literal: true class UserMailer < ApplicationMailer layout 'mailer' def invite invitation = params[:invitation] email = invitation.email subject = 'Invitation email' # The HTML body of the email htmlbody = render_to_string( template: 'user_mailer/invite', layout: true, locals: { email: receiver_email, } ) send_email( receiver: receiver_email, sender: 'default@email.com', subject: subject, body: htmlbody ) end def send_email(receiver:, sender:, subject:, body:) AWS_SES_CLIENT.send_email( { destination: { to_addresses: [receiver] }, message: { body: { html: { charset: 'UTF-8', data: body } }, subject: { charset: 'UTF-8', data: subject } }, source: sender } ) rescue Aws::SES::Errors::ServiceError => e Rails.logger.info "ERROR! Email for #{receiver} with subject #{subject} wasn't sent with error message: #{e}" end end
spec/mailers/preview/user_mailer_preview.rb 预览类实现如下:
# frozen_string_literal: true # Preview all emails at http://localhost:3000/rails/mailers/user_mailer class UserMailerPreview < ActionMailer::Preview def invite UserMailer.with(invitation: UserInvitation.first).invite end end
报错信息
访问预览地址http://localhost:3000/rails/mailers/user_mailer时,页面返回如下提示:
You are trying to preview an email that does not have any content.
This is probably because the mail method has not been called in
user_mailer#invite.
初步排查
我推测Rails原生邮件预览器的实现逻辑是追踪mail方法的调用,而我当前通过自定义的send_email方法直接调用AWS SES接口发信,未调用ActionMailer原生的mail方法,因此预览功能无法正常工作。查阅AWS SDK官方指南未找到对应解决方案,想确认是否可以通过调整action_mailer的相关配置解决该问题。
问题原因
你的判断完全正确:Rails 原生邮件预览的工作逻辑是捕获 mailer 动作返回的 Mail::Message 对象、读取其内容渲染页面,只有调用 ActionMailer 内置的 mail 方法才会生成这个对象。你当前直接在 mailer 里调用 AWS SES SDK 发信,动作最终返回的是 SES 接口响应值(异常捕获后为nil),预览器拿不到邮件内容,就会抛出上述报错。
不建议在 mailer 中硬编码 SES 客户端调用逻辑,ActionMailer 原生支持 SES 作为投递适配器,不需要手动对接SDK,同时可以完美兼容预览、测试环境邮件拦截、批量投递配置等所有内置功能。
修复方案
- 移除 mailer 中自定义的
send_email方法,删除手动实例化AWS_SES_CLIENT发信的逻辑
- 移除 mailer 中自定义的
- 配置 ActionMailer 投递方式
生产环境在config/environments/production.rb中添加配置:
本地开发环境不需要额外修改投递配置,默认的config.action_mailer.delivery_method = :ses:test或常用的:letter_opener配置都不会影响预览功能。SES 投递适配器会自动读取本地已配置的 AWS 凭证,和你之前使用aws-sdk-ses的凭证逻辑完全一致,不需要额外调整。- 配置 ActionMailer 投递方式
- 重构 mailer 方法为 ActionMailer 标准写法,移除手动
render_to_string的逻辑,直接用内置mail方法构造邮件对象,模板变量用实例变量传递即可:
注意原代码存在笔误:局部变量# frozen_string_literal: true class UserMailer < ApplicationMailer layout 'mailer' def invite invitation = params[:invitation] @receiver_email = invitation.email mail( to: @receiver_email, from: 'default@email.com', subject: 'Invitation email' ) end endemail赋值后未使用,后续调用的是未定义的receiver_email,重构后直接用实例变量传递给模板即可,不需要手动渲染模板字符串。- 重构 mailer 方法为 ActionMailer 标准写法,移除手动
- 保持现有预览类代码不变,重启本地服务后再访问预览地址即可正常展示邮件内容。
特殊场景兼容(不推荐)
如果有特殊原因必须保留手动调用 SES SDK 的逻辑,可以在方法中判断当前运行环境:预览/开发环境调用原生 mail 方法返回 Mail::Message 对象供预览使用,生产环境再走手动 SES 调用逻辑。但这种写法会重复维护邮件构造逻辑,后续改动容易出现多环境逻辑不一致的问题,优先使用官方适配器方案。
内容的提问来源于stack exchange,提问作者Artem Stepanenko

