当前位置: 首页 > 工具软件 > SES-Mailer > 使用案例 >

使用 Action Mailer

宋望
2023-12-01

一,Base

我们 Mailer 类继承的 ActionMailer::Base 指的就是它。

作用

它继承于 AbstractController::Base,包含了一些自身及 Abstract Controller 的模块(尽管有的模块它并没有使用到),作用是为了让它的子类(我们的 Mailer 类)能够”更好用、更实用”。

     YourMailer
         |
         V
  ActionMailer::Base
         |
         V
AbstractController::Base

包含内容

包含了一些默认配置 default_params.

注册拦截器(interceptor)或观察者(observer)。

请求到邮件处理 process.

创建邮件实例
(因为 Base include 了多个模块,所以这个实例可以使用这些模块所定义的方法)。

邮件(mail)、附件(attachments)

mail(headers = {}, &block) 表示邮件对象。
可接收一个代码块做为参数,header(头部)可接受:

参数含义
:subject主题
:from发件人
:to收件人
:cc抄送
:bcc密送
:reply_to回邮地址
:date时间

NOTE: 想了解更多 header,点击 Email#Header_fields

使用举例:

def welcome(user)
  @user = user

  mail(to: @user.email, subject: 'Welcome to My Awesome Site')
end

attachments() 允许你在邮件里添加附件。

类型:

a_mailer.attachments.class
=> Mail::AttachmentsList

# 类似数组
a_mailer.attachments
=> []

使用举例:

# 文件名的方式
mail.attachments['filename.jpg'] # => Mail::Part 实例对象或 nil

# 索引的方式
mail.attachments[0]              # => Mail::Part (第一个实例对象)

只需要指定文件名、文件类型,然后 Rails 会自动帮你计算出 Content-Type, Content-Disposition, Content-Transfer-Encoding 和 base64 编码等附件内容。

你也可以重写它们:

mail.attachments['filename.jpg'] = { mime_type: 'application/x-gzip',
                                     content: File.read('/path/to/filename.jpg') }

设置默认值(default & default_options=)

default(value = nil) 是ActionMailer::Base提供的方法,用来设置 default_params,默认已经有:

default_params = {
  mime_version: "1.0",
  charset:      "UTF-8",
  content_type: "text/plain",
  parts_order:  [ "text/plain", "text/enriched", "text/html" ]
}

我们可以配置(value 必需是 Hash 类型):

# 下面两者一样
config.action_mailer.default { from: "no-reply@example.org" }
config.action_mailer.default_options = { from: "no-reply@example.org" }

# 常见配置(开发模式下)
config.action_mailer.default_url_options = { :host => 'localhost:3000' }

# 配置邮件发送方式
config.action_mailer.delivery_method = :smtp

# 根据不同发送方式,做不同配置
config.action_mailer.smtp_settings = {
  address: 'smtp.gmail.com',
  port: 587,
  domain: 'your_domain.com',
  # 完整的邮箱地址
  username: 'your_email@gmail.com',
  password: 'your_gmail_password',
  authentication: 'plain',
  enable_starttls_auto: true
}

# 邮件发送报错时(比如:邮箱错了),是否抛异常。开发环境下,可设置为 true
config.action_mailer.raise_delivery_errors = true

# 是否真的发邮件,默认已经是 true
config.action_mailer.perform_deliveries = true

它和配置文件里的 default_options= 是一样。

在 config/environments 目录下针对不同执行环境会有不同的邮件服务器设置:

config.action_mailer.default_options = { from: "no-reply@example.org" }

前者对当前 Controller 及其子类有效,而后者对当前环境下所有 Controller 有效。除了使用的地方不同,导致作用域稍有不同外,两者本质是一样的。

设置头部消息 headers

使用 headers 可以设置邮件的头部消息,例如:

headers['X-Special-Domain-Specific-Header'] = "SecretValue"

headers 'X-Special-Domain-Specific-Header' => "SecretValue",
        'In-Reply-To' => incoming.message_id

直接调用了 Mail::Message#headers 方法,默认已经有选项:

:subject
:sender
:from
:to
:cc
:bcc
:reply-to
:orig-date
:message-id
:references

接收邮件 receive

Rails 处理邮件,不常用,而且会比较耗费资源,所以不推荐。但如果你要用的话,你可以实现 receive(raw_mail) 方,唯一的参数就是,接收到的邮件内容。
Rails 会先创建对应的 Mail 邮件对象,之后才进行后续处理。

创建邮件对象时的魔法

细心的你应该发现,我们在 Mailer 类里定义的是实例方法,但创建 mailer 对象用的却是类方法。

这里隐藏着魔法,当找不到此类方法时,就会调用 Rails 重新实现的 method_missing 类方法, 会先检查 action_methods 里是否有同名方法,如果有,则(把此方法当做参数对待)创建 MessageDelivery 对象。

拦截器 register_interceptor

为了”开发和测试尽量的接近生产环境”和”知道发送出去的邮件真正的样子”等目的,我们希望在非生产环境下,能够查看邮件发送情况。

解决方案

  • (开发、测试)用邮件预览 gem

我们可以使用 mailcatcher 或 letter_opener 等 gem 实现。

  • (生产)只发送到特定的邮箱

比如,我们注册一些特殊账号或邮箱,然后发送给我们自己。

  • 用拦截器

也就是下面要介绍的。

注册拦截器一(可指定条件):

if Rails.env.development?
  ActionMailer::Base.register_interceptor(DevelopmentMailInterceptor)
end

注册拦截器二(没有加载的话,需要先加载):

require 'whitelist_interceptor'
ActionMailer::Base.register_interceptor(WhitelistInterceptor)

注册拦截器三(没有加载的话,需要先加载):

require 'environment_interceptor'
ActionMailer::Base.register_interceptor(EnvironmentInterceptor)

上述代码,一般放在 config/application.rb
或 config/environments/file_name.rb
或 config/initializers/file_name.rb
文件里。

实现 delivering_email 方法一,
在最后时刻替换掉要发送到的邮箱。

class DevelopmentMailInterceptor
  def self.delivering_email message
    message.subject = "[#{message.to}] #{message.subject}"
    message.to = "overwrite_to@example.com"
  end
end

实现 delivering_email 方法二,白名单,群发公司邮箱。

class WhitelistInterceptor
  def self.delivering_email message
    unless message.to.join(' ') =~ /(@yourcompany.com)/i
      message.subject = "#{message.to} #{message.subject}"
      message.to = ENV['NOTIFICATIONS_EMAIL']
    end
  end
end

实现 delivering_email 方法三,非生产环境时,标明发邮件所在的运行环境。

class EnvironmentInterceptor
  def self.delivering_email message
    unless Rails.env.production?
      message.subject = "[#{Rails.env.capitalize}] #{message.subject}"
    end
  end
end

上述代码,一般放在 lib/file_name.rb 文件里。

上面大概思路已经给出,实际项目如何使用可以自行决定。

订阅者 register_observer

邮件发送之后想做点什么?– 使用观察者。

类似 register_interceptor,注册订阅者:

class MailObserver
  # 实现 delivering_email 方法
  def self.delivered_email message
    # ...
  end
end

ActionMailer::Base.register_observer(MailObserver)

注意:这里实现的是 delivered_email 方法,而之前实现的是 delivering_email 方法。

二,Message Delivery

应用层面的邮件发送。

YourMailer#action 时已经使用,创建的就是 Message Delivery 实例对象。

实例方法:

deliver_now
deliver_now!

deliver_later
deliver_later!

message

deliver_laterdeliver_later! 接受参数 :wait、:wait_until 或 :queue.

message 表示邮件对象,即 Mail::Message 实例对象。

执行 deliver 操作时,会得先到 message 对象,然后直接调用 gem ‘mail’ 提供的方法,或加入延迟任务再调用。

通常,我们都是创建邮件并发送:

Notifier.welcome("helloworld@example.com").deliver_now

也可以先创建邮件对象,稍后邮件对象调用方法发送邮件:

message = Notifier.welcome("helloworld@example.com")
# => 得到 ActionMailer::MessageDeliver 实例对象

message.deliver_now
# 执行发送邮件

获取 Mail::Message 实例对象(deliver 操作其实是由它发出):

Notifier.welcome(david).message

Note: 这里的邮件”发送”,指的是我们应用层面的”发送”。至于 Rails 底层如何实现邮件”发送”,参考【Delivery Methods】章节。

三,Mail Helper

几个邮件相关的 Helper 方法。

如:attachments、mailer、message,还有不太实用的 block_format、format_paragraph.

方法解释
message()表示邮件对象,即 Mail::Message 实例对象
attachments()表示邮件里面的附件
format_paragraph(text, len = 72, indent = 2)格式化文字内容。默认行首空两格,每行长度不超过 72 个字符
block_format(text)使用上面的 format_paragraph 来处理大段的文字内容
mailer()YourMailer 的实例对象,类似 YourController 的实例对象

四,Delivery Methods 定制与新增

底层如何实现邮件”发送”?

Rails 本身没有实现此功能,但提供选择:调用 Rails 已有发送程序,使用已有邮件服务,新增。

调用 Rails 已有发送程序

delivery_method 默认已经有:

:smtp     => Mail::SMTP
:file     => Mail::FileDelivery
:sendmail => Mail::Sendmail
:test     => Mail::TestMailer

我们可以根据需求,选择、配置、使用它们。

使用已有邮件服务

  • 如使用 gem ‘letter_opener’ 直接配置:
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener
  • 如使用 gem ‘aws-ses’ 配置:
ActionMailer::Base.add_delivery_method :ses, AWS::SES::Base,
  :access_key_id     => 'abc',
  :secret_access_key => '123'

然后:

config.action_mailer.delivery_method = :ses

新增

完全的”新增”邮件服务,难度上比较大。比较实际的做法是”继承”于已有的实现,并且在 Rails 里注册一下,然后使用。

1) “继承”于已有的实现(这里是 SMTP):

require 'mail'

class CustomSmtpDelivery < ::Mail::SMTP
  # SMTP 配置
  def initialize(values)
    self.settings = {:address => "smtp.gmail.com",
                     :port => 587,
                     :domain => 'yourdomain',
                     :user_name => "username",
                     :password => "password",
                     :authentication => 'plain',
                     :enable_starttls_auto => true,
                     :openssl_verify_mode => nil
                    }.merge!(values)
  end

  # 或者,把 SMTP 配置放到配置文件里
  def initialize(options = {})
    self.settings = options
  end

  attr_accessor :settings

  # 必须重新实现 deliver! 方法
  def deliver!(mail)
    # 把收箱人替换成指定的,这里功能类似拦截器
    mail['to'] = "to@example.com"
    mail['bcc'] = []
    mail['cc'] = []
    super(mail)
  end
end

2) 在 Rails 里注册一下:

add_delivery_method :custom_smtp_delivery, CustomSmtpDelivery

3) 配置使用刚才新增的 delivery method:

# config/environments/development.rb
config.action_mailer.delivery_method = :custom_smtp_delivery

在之后配置就变成了:

ActionMailer::Base.custom_smtp_delivery_settings = {
  :address => "smtp.gmail.com",
  :port => 587,
  # ...
}

Rails 已有发送程序及 gem ‘letter_opener’ 就是用这种方式定义,之后提供给我们使用的。

五,Previews & Preview

邮件预览相关。

Previews,对于普通开发者来说主要是配置:

# 配置预览文件存放的位置,默认如下:
config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews"

# 配置是否允许邮件预览。开发模式下,默认为 true
config.action_mailer.show_previews = true

默认可以到以下 url, 查看预览邮件:

http://localhost:3000/rails/mailers/

Preview,是我们自定义 YourPreview 的父类,提供一些普通 Web 开发者察觉不到的方法,如:

preview_name 返回自定义类名,但把 “Preview” 后缀去掉。如 YourPreview 返回 “Your”

emails 返回所有可预览的邮件

这里的预览,和 Mail CatcherLetter Opener 等提供的预览不同,它属于规范的测试,而后者更类似于人肉测试。

Note: 邮件预览,在 Rails 里也遵守 MVC. M 是 ActionMailer::Preview,V 是 rails/mailers/,C 是 Rails::MailersController

 类似资料: