OneDay

40歳からプログラマとして生活しています。

Railsでメール配信してみる。 Action Mailer

Webアプリケーションの顧客の「誕生日おめでとう」とメールを配信する機能を実装していきたいと思います。朝の9時に当日誕生日の顧客を拾い上げて、「#{User.name}さん、おめでとう」と配信させたいと思います。しかし、9時は通常アクセスでトラフィックが増える時刻。かと言ってトラフィックが少ない夜中にジョブが動いてメールが届くと顧客に失礼にあたります。
そこで、バックグランド処理でメール配信する方法を考えていきたいと思います。

Userというモデルがある前提とします。

  id: integer
  created_at: datetime
  updated_at: datetime
  email: string
  birthday: date
  name: string

3人任意の人を作り、1人の誕生日を昨日、残りの誕生日を本日、明後日とします。pry(irb)でこんな感じで作成します。今日の誕生日はUser_1さんです。

[1] pry(main)> 3.times do |i|
[1] pry(main)*   User.create(email: "your_email@example.com", name:"User_#{i}", birthday: (Date.yesterday+i))
[1] pry(main)* end


ActionMailerを用いて実装していきます。手始めにちょっと触れてみます。
mailer: welcome_mailer、アクション(メソッド): greetingをジェネレーターで作ります。

bin/rails g mailer WelcomeMailer greeting
#生成されるもの
 create  app/mailers/welcome_mailer.rb
      create  app/mailers/application_mailer.rb
      invoke  erb
      create    app/views/welcome_mailer
      create    app/views/layouts/mailer.text.erb
      create    app/views/layouts/mailer.html.erb
      create    app/views/welcome_mailer/greeting.text.erb
      create    app/views/welcome_mailer/greeting.html.erb
      invoke  test_unit
      create    test/mailers/welcome_mailer_test.rb
      create    test/mailers/previews/welcome_mailer_preview.rb

app/mailersに生成されたものを確認します。

#app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout 'mailer'
end
#app/mailers/welcome_mailer.rb
class WelcomeMailer < ApplicationMailer

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.hello_mailer.greeting.subject
  #
  def greeting
    @greeting = "Hi"

    mail to: "to@example.org"
  end
end

WelcomeMailer < ApplicationMailer < Action::Mailer、 こんな階層になっています。
app/views/welcome_mailerも確認します。@greetingがあり、app/mailers/welcome_mailer.rbにあったインスタンス変数を引っ張ってきます。

#app/views/welcome_mailer/greeting.html.erb
<h1>WelecomeMailer#greeting</h1>

<p>
  <%= @greeting %>, find me in app/views/welcome_mailer/greeting.html.erb
</p>
#app/views/welcome_mailer/greeting.text.erb
WelcomeMailer#greeting

<%= @greeting %>, find me in app/views/welcome_mailer/greeting.text.erb

一旦Action Mailerを離れて、アプリケーションにGmailにてメール配信する設定を施します。

# config/environments/development.rb
Rails.application.configure do
  (略)
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
      address:              'smtp.gmail.com',
      port:                 587,
      user_name:            'あなたのGメールアドレス',
      password:             '2段階認証で得たパスワード',
      authentication:       'plain',
      enable_starttls_auto: true  }
  (略)
end


再びActionMailerに戻り、実際にメールが飛ばせるか設定を以下に変え試してみたいと思います。

#app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'Your_Name <あなたのGメールアドレス>'
  layout 'mailer'
end
#app/mailers/welcome_mailer.rb
class WelcomeMailer < ApplicationMailer
 def greeting(email, name)
    @greeting = "Hi, #{name}"
  
    mail to: email
  end
end
#app/views/welcome_mailer/greeting.html.erb

<p>
  <%= @greeting %>さん、おめでとう! 
</p>

pry(irb)からメールの処理を行います。本日が誕生日の人にだけ送ります。

[1] pry(main)> users = User.where(birthday: Date.today)
[2] pry(main)> users.each do |u|
[2] pry(main)*   HelloMailer.greeting(u.email, u.name).deliver_now
[2] pry(main)* end

User_1さん宛にメールが届きます。

Hi, User_1さん、 おめでとう!



次はバックグランド処理でメールを送ります。この処理には非同期処理のGemが必要で、ここではSidekiqを用いました。(バックグランドの処理をするRedisが必要です。redis-serverで起動しておいてください。)

#Gemfile
(略)
gem 'sidekiq'
(略)

と記述し、bin/bundle。bundle exec sidekiq -q default などとキューの名前を指定して別プロセスの処理ためsidekiqを立ち上げます。このAction Mailerはmailersというキューの名前が予め用意されているので、bundle exec sidekiq -q mailersというコマンドでAction Mailerの別処理が起動します。それに加え、ActionMailerにdeliver_laterというメソッドをセットすれば非同期処理ができます。わかりにくいので、ちょっと手を動かしてみます。3つのターミナルのタブを用意し、それぞれRails アプリケーションのルートに移動します。

1つ目のタブ:redis-server をうって、redisを立ち上げます。
2つ目のタブ:bin/bundle exec sidekiq -q mailers をうって、sidekiqを立ち上げます。
3つ目のタブ : pry(irb)から5分後に処理されるコマンドを打ちます。先ほどの誕生日メールの例をここでもやってみます。

[1] pry(main)> users = User.where(birthday: Date.today)
[2] pry(main)> users.each do |u|
[2] pry(main)*   HelloMailer.greeting(u.email, u.name).deliver_later(wait: 5.minutes)
[2] pry(main)* end


5分後、果たしてUser_1さん宛にメールが届きます。

Hi, User_1さん、 おめでとう!

Active Job · mperham/sidekiq Wiki · GitHub
deliver_laterというメソッドはdeliver_later(wait_until: 10.hours.from_now)ような使い方もできますし、sidekiqに処理を回すだけならば引数を取らずにdeliver_laterとすればよいようです。





最後に毎日ある時刻に、繰り返して、ここまで行った処理が出来れるようにしたいと思います。まずは上記でやった誕生日の人を抜き出してメールを送るというのをタスク化したいと思います。

bin/rails g task mail
   create  lib/tasks/mail.rake
#lib/tasks/mail.rake
namespace :mail do
    desc "asynchronous mailing process"
    task mail_delivery: :environment do
        users = User.where(birthday: Date.today)

        users.each do |u|
            HelloMailer.greeting(u.email, u.name).deliver_later
        end
     end
end

このタスクが機能するか、先ほどと同じようにメールが届くか確認します。

bin/rake mail:mail_delivery --trace 
** Invoke mail:mail_delivery (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute mail:mail_delivery

はい。ちゃんとメールも届きます。


そして、この処理をスケジュール化します。wheneverというgemを用います。

#Gemfile
(略)
gem 'whenever'
(略)

bin/bundle後に、ルートディレクトリでwheneverize .とコマンド打つととconfig/schedule.rbが作成されます。

set :output, "log/crontab.log"
set :environment, :development

every 2.minutes do
	rake "mail:mail_delivery"
end

# every 1.days, at: '9:00 am' do
       # rake "mail:mail_delivery"
# end

テストのために2分ごとに処理がまわるように書いています。
あとはルートディレクトリにて

whenever -i
[write] crontab file updated

すれば、このジョブを登録でき、2分後にメールが届きます。「おめでとう」って。メールが届くことを確認したら、whenever -cで処理を止めます。schedule.rbでevery 1.days, at: '9:00 am' doの方を有効にすると毎朝9:00に処理が回ります。


whenever とcrontabコマンド

whenever -i #shedule.rbの内容をcron化します。
whenever  #shedule.rbの内容をcronの構文で置き換えて表示します。
whenever -c #キャンセル
crontab -l #スケジュール化されている内容
crontab -r # キャンセル