📫

Action Mailerを利用したメールJobがエンキューされるまでの流れ

2023/12/18に公開

初めに

ポート株式会社 サービス開発部 Advent Calendar 2023 17日目の記事です。

ポート株式会社サービス開発部に所属している原です。業務では、Railsを用いてバックエンド開発を行なっています。

調査動機

業務でメール送信周りでバグが起こり、Action Mailerでのメール送信周りの処理が気になったので、今回コードリーディングをしてみました。

Railsでメール送信する場合の流れ

Railsはメール送信する場合は、Action Mailerを利用します。また、非同期でメールを送信する場合は、Active Jobも利用されるので今回は、Action MailerActive Jobそれぞれコードリーディングしていこうと思います。

Action Mailerとは?

https://github.com/rails/rails/tree/main/actionmailer
Action MailerはRailsに含まれているライブラリであり、メール送信に関する機能が実装されています。
基本的な使い方は下記Railsガイドを参照してください。
https://railsguides.jp/action_mailer_basics.html

Active Jobとは?

https://github.com/rails/rails/tree/main/activejob
Active JobはRailsに含まれているライブラリであり、Job操作に関する機能が実装されています。実際にエンキューする場合はアダプタ経由でエンキューします。
基本的な使い方は下記Railsガイドを参照してください。
https://railsguides.jp/active_job_basics.html

コードリーディング編

Action Mailer部分

自分が普段開発しているプロダクトのRailsのバージョンが7.0.8なので、同様のバージョンをコードリーディングしていこうと思います。また、実際のプロジェクトのメイラー名などは使用せず、RailsガイドからMailer名やメソッド名などを拝借させていただいています。

UserMailerMailer.welcome_email.deliver_later
上記のようにメール送信処理を行なったときは、まず下記部分が呼ばれます。
https://github.com/rails/rails/blob/v7.0.8/actionmailer/lib/action_mailer/message_delivery.rb#L98-L100
早速エンキューしようとしてますね。UserMailerMailer.welcome_email.deliver_later(wait: 1.minute)などを渡した場合は変数optionsに{:wait=>1 minute}が入るようです。

正常にメールが送信される場合は、下記のelse句の内容が呼ばれます。
https://github.com/rails/rails/blob/v7.0.8/actionmailer/lib/action_mailer/message_delivery.rb#L142-L145
コード内の@mailer_class.delivery_jobActionMailer::MailDeliveryJobのようでした。このJobに対して、mailerのクラスやアクション、mailerに渡した引数などを渡しています。
下記から#perform_later以降の処理はActive Jobを読みにいく必要があることはわかったのですが、一旦ActionMailer::MailDeliveryJob内のコードを読みにいきます。

@mailer_class.delivery_job.set(options).method(:perform_later).source_location
=> ["/app/rails/activejob/lib/active_job/configured_job.rb", 14]

ActionMailer::MailDeliveryJobのコードは下記になります。
https://github.com/rails/rails/blob/v7.0.8/actionmailer/lib/action_mailer/mail_delivery_job.rb#L11
ActionMailer::MailDeliveryJobは、ActiveJob::Baseを継承して作成されたJobになってますね。

Active Job部分

今度はActive Jobの中を見にいきます。上述したようにメールを非同期で送信するようにした場合は、下記コードが参照されます。
https://github.com/rails/rails/blob/v7.0.8/activejob/lib/active_job/configured_job.rb#L14-L16

実際のエンキューの処理は下記で行われてます。
https://github.com/rails/rails/blob/v7.0.8/activejob/lib/active_job/enqueuing.rb#L59

開発しているプロジェクトではActive JobのアダプタはSidekiqが使用されていますので、下記部分でActiveJob::QueueAdapters::SidekiqAdapter内の#enqueueが呼ばれます。
https://github.com/rails/rails/blob/v7.0.8/activejob/lib/active_job/enqueuing.rb#L67

ActiveJob::QueueAdapters::SidekiqAdapter#enqueueでは、Sidekiq::Clientがpushしてますね。
https://github.com/rails/rails/blob/v7.0.8/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb#L20-L27

ここからredisなどにエンキューする処理を見にいくには、Sidekiq内のコードを見にいく必要がありますが、そこはまた次回にしようと思います!

終わりに

今回Rails内のコードを読んでみて、普段何気なく使用している機能が裏でどのように動いているか知ることができました。
これからもRailsなどのOSSのコードは積極的に読んで行くようにしたいです。

Discussion