Railsでメール自動配信機能をつくるまでの道程

※ _「[あなたがRails触る人なら見ておきたい「体系的な」豆知識](http://qiita.com/Yama-to/items/93cd8b3a8be67b09e353)」からの派生記事です。_

「mailersってデフォルトで用意生成されるけどあんま使ったことない」そう思った8月某日、RailsでAction Mailerを利用して開発環境でメール自動配信機能をつくってみました。

## 手順
1. `development.rb` にメール送信設定を記述
2. メーラーを生成
3. メーラーを編集
4. メール本文をデザインする
5. メーラーを呼び出すためのメソッドを記述する
6. 実際に表示を確認する
7. (発展) 添付ファイル付きのメールを送信する

## 1. `development.rb` にメール送信設定を記述

メールを送信する時には送信するサーバーが必要です。
今回はgmailのアカウントからメールを送れるようにしてみます。

```development.rb
Rails.application.configure do
  #--- 中略 ---#
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    port:                 587,
    address:              'smtp.gmail.com',
    domain:               'smtp.gmail.com',
    user_name:            '<YOUR EMAIL ADDRESS>',
    password:             '<YOUR EMAIL PASSWORD>',
    authentication:       'login',
    enable_starttls_auto: true
  }
end
```

`config.action_mailer` というパラメーターに様々なオプションを指定する事ができます。
上から順番に説明していきます。

- `raise_delivery_errors`

  メールの送信に失敗した時にエラーを発火させるか (デフォルト値: `false`)

- `delivery_method`

  メールを送信する方法 (デフォルト値: `:smtp`)

- `smtp_settings`

  :smtpモードでの設定情報
  
  1. `address` => SMTPサーバーのホスト名
  2. `port` => SMTPサーバーのポート番号
  3. `domain` => HELOドメイン
  4. `user_name` => メール送信に使用するgmailのアカウント
  5. `password` => メール送信に使用するgmailのパスワード
  6. `authentication` => 認証方法
  7. `enable_starttls_auto` => メールの送信にTLS認証を使用するか

_※ 後にGmail以外のクライアントを使用する際に`hostname was not match with the server certificate`というエラーが出続ける場合には`enable_starttls_auto`の項目を`false`としてください。_

## 2. メーラーを生成

メール送信におけるコントローラー的役割を果たす`Mailer`を`railsコマンド`で生成します。

```bash
$ rails g mailer SampleMailer send_when_update

# rails g mailer <メーラー名> <メソッド名>
```

`app/mailersフォルダ`の配下に`application_mailer`と`sample_mailer`が生成されます。
`app/views/sample_mailer`には同じくメールテンプレートファイルが生成されます。

以後は基本的にこれら2つをカスタムしていきます。

## 3. メーラーを編集

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

```app/mailers/sample_mailer.rb
class SampleMailer < ApplicationMailer
  def send_when_update
    @greeting = "Hi"
    mail to: "to@example.org"
  end
end
```

コメント等は省いていますが、生成直後の状態はこうなっているはずです。

`application_mailer`には全メーラー共通の設定を、
`sample_mailer`にはメーラー個別の設定を、それぞれ記述していきます。

### application_mailerの編集

```app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from:     "メールテスト運営局",
          bcc:      "sample+sent@gmail.com",
          reply_to: "sample+reply@gmail.com"
  layout 'mailer'
end
```

共通の処理・設定を記述する場合には`defaultメソッド`を使用します。
メールに関して指定可能な主なプロパティは以下のとおりです。

|名前|概要|
|:--|:--|
|to|通常送信先|
|cc|一斉送信先|
|bcc|非表示送信先|
|from|メールの送信元名|
|subject|メールタイトル|
|date|メールの送信日時|
|reply_to|返信先アドレス|
|content_type|メール本文の種類|

今回の場合はメールの送信元名を設定しました。
アドレス以外に「メールテスト運営局」という名前が表示されるようになります。

### sample_mailerの編集

```app/mailers/sample_mailer.rb
class SampleMailer < ApplicationMailer
  def send_when_update(user)
    @user = user
    mail to:      user.email,
         subject: '会員情報が更新されました。'
  end
end
```

メールに関する個別の設定には`mailメソッド`を使用します。
この時設定可能なプロパティは上掲の表のとおりです。

今回の場合は`send_when_updateメソッド`を呼び出す際に渡されるユーザーの情報から、
emailアドレスだけを取り出してメールの送信先としています。
こうすることでメール送信先を動的に変更することが可能です。

またメールテンプレート内で動的な変数を使用したい場合にはここで定義します。
メーラーはコントローラー的役割を果たしていると言った意味が実感できると思います。

`mailメソッド`が呼び出された後は、自動的にメール本文のテンプレートを読み込みます。
次はメールの本文を編集していきましょう。

## 4. メール本文をデザインする

メール本文のテンプレートはデフォルトで二種類の形式が提供されています。

```views/sample_mailer/send_when_update.html.erb
<h1>SampleMailer#send_when_update</h1>

<p>
  <%= @greeting %>, find me in app/views/sample_mailer/send_when_update.html.erb
</p>
```
```views/sample_mailer/send_when_update.text.erb
SampleMailer#send_when_update

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

「 **HTMLメールが送信できない場合にはテキストベースのメールを送信する** 」
といったRailsの動作を保証するために二種類とも記述してある事が望まれます。

先ほど`sample_mailer`で定義したインスタンス変数を利用して、
動的にテキストの内容を表示できるようにしましょう。

```views/sample_mailer/send_when_update.html.erb
<!doctype html>
<html lang="ja">
<head>
  <meta content="text/html; charset=UTF-8" />
  <style type="text/css">
    h2 {
      color: #e7454a;
    }
    p hr {
      color: #2d2a24;
    }
  </style>
</head>
<body>
  <h2><%= @user.name %> 様</h2>
  <hr />
  <p>
    この度は「メールテスト運営局」を利用頂きましてありがとうございます。
  </p>
  <p>
    ユーザー名: <%= @user.name %><br />
  </p>
  <hr />
</body>
</html>
```
```views/sample_mailer/send_when_update.text.erb
===============================
<%= @user.name %>様
===============================

この度は「メールテスト運営局」を利用頂きましてありがとうございます。
ユーザー名: <%= @user.name %>
```

## 5. メーラーを呼び出すためのメソッドを記述する

メールの設定を記述しただけではメールは送信できません。
メーラーは各コントローラーのアクションからの呼び出しによって起動します。

```controllers/users_controller.rb
class UsersController < ApplicationController
  def update
    current_user.update(update_params)
    SampleMailer.send_when_update(current_user).deliver
  end
end
```

ユーザー情報が更新された直後に、ユーザー宛にメールが送信されるよう記述した例です。

メーラーは「`メーラー名`.`メソッド名`」として、クラスメソッドを呼び出すかのように実行できます。
今回はメーラー側でユーザーの情報を利用して、動的に宛先や本文を変更する実装を行っています。
そのためメソッドの引数としてユーザーのインスタンスを渡しています。

実際の送信を担うのは`deliverメソッド`です。
メーラーを起動して返ってきたメールのデータを送信します。

## 6. 実際に表示を確認する

ユーザーの編集機能は適当にscaffoldで生成したものがあれば十分です。
実際に編集してみましょう。

＼\ﾎﾟﾁｯ!/／
![メールタイトル](https://i.gyazo.com/81f3a7801330db6b00736760cfd4e613.png)

自分のユーザー情報として登録したGmailに届きました。
送信元は「メールテスト運営局\<sample@gmail.com\>」となっています。

メール本文を開くと…

![メール本文](https://i.gyazo.com/c76bd777c8c40ec385567f1cd0af0edd.png)

うん、きちんとHTML形式で本文が表示されていますね。

## 7. (発展) 添付ファイル付きのメールを送信する

ユーザーがアバター画像としてアップロードしたファイルを、メールに添付したいという欲求にかられトライした記録を残しておきます。

以下は多少見やすいように、改行を増やして表示しています。

```app/mailers/sample_mailer.rb
class SampleMailer < ApplicationMailer
  def send_when_update(user)
    @user = user
    @avatar_file = avatar_file_name(user)

    # set attachment file
    attachments[@avatar_file] =
      File.read(Rails.root.join("public#{avatar_url(@user)}"))

    # set mail header
    mail to:      @user.email,
         subject: '会員情報が更新されました。'
  end

  private

  def avatar_url(user)
    user.avatar.url(:thumb).gsub(/\?\d*/, "")
  end

  def avatar_file_name(user)
    "avatar#{avatar_url(user).match(/(\..+)/)[0]}"
  end
end
```

メールにファイルを添付するには`attachmentsメソッド`を使用します。
`attachmentメソッド`のキーにはファイル名を、バリューにはファイル本体を指定します。

以下、順を追って解説します。

### キモはattachmentメソッド

まずアバター画像は「20150808235932.png」のようなファイル名で、
`public/system/users/avatars/...` のディレクトリに保存してあります。

> @avatar_file = avatar_file_name(user)

メールテンプレートでも添付ファイル名を使用できるように、インスタンス変数を定義しています。

> def avatar_file_name(user)
>   "avatar#{avatar_url(user).match(/(\..+)/)[0]}"
> end

avatar_file_nameメソッドを呼び出すことで、添付ファイル名を
「avatar.jpg」や「avatar.png」といった「 **avatar.`拡張子`** 」の形に成形しています。

> attachments[@avatar_file] = 
> File.read(Rails.root.join("public#{avatar_url(@user)}"))

ファイルの添付には`attachmentsメソッド`を使用するのでしたね。

先ほど成形したファイル名を添付ファイル名にしています。
ファイル本体を読み込むには`File.readメソッド`の引数にファイルの絶対パスを指定すると良いです。
今回の場合はアプリケーションのルートパスに、public以下のパスを`joinメソッド`で結合しています。

> def avatar_url(user)
>   user.avatar.url(:thumb).gsub(/\?\d*/, "")
> end

`avatar_urlメソッド`はファイルを呼び出した際にファイル名に付与されてしまうハッシュ値を、
取り除くために定義・実行しています。

### 表示を確認してみる

＼\ﾎﾟﾁｯ!/／
![avatar画像](https://i.gyazo.com/e09e7fd670d9d75bc30619bba81e6325.png)

添付できとるうううぅぅぅ

### 添付画像のインライン表示化

ちなみに、以下のように細工することで添付画像をインライン表示することも可能です。

```app/mailers/sample_mailer.rb
attachments.inline[@avatar_file] =
  File.read(Rails.root.join("public#{avatar_url(@user)}"))
```
```views/sample_mailer/send_when_update.html.erb
<p>
  ユーザー名: <%= @user.name %><br />
  アバター画像:<br />
  <%= image_tag attachments[@avatar_file].url %>
</p>
```

「attachments`.inline`」とメソッド内で定義していること、
「image_tag attachments[@avatar_file]`.url`」とテンプレート内で呼び出していること、
この2つが必要条件です。早速動作を確認してみましょう。

![avatar_inline画像](https://i.gyazo.com/5f1a57414380af772ef6bb478933cf77.png)

先ほどとは異なり、メール本文にそのまま画像が取り込まれているため
より自然で伝わりやすい文章となっていますね。

## さいごに
今回はRailsでデフォルト搭載されているメーラーを、食べず嫌いになっている方向けに書いたつもりです。
ざっとメーラーを使っていくのに必要な事項はまとめられたかなと思います。

しかしまだ課題はあります。
今のコードのままだとメールを送信する際どうしても数秒のバッファが生じてしまうのです。
理想としては、アクションを実行した後さっさとテンプレートの描画に進み、
メールの送信はバックグランドで勝手にやっていて欲しいですね。
どうすればできるようになるか、皆さんも考えてみてください！