Rails6 アプリからGmailのメールサーバからメールを送信する ローカル環境編

## 目的

- Rails6で作成したアプリからGmailのメールサーバを利用してメールを送信する方法をまとめる。

## 前提条件

- Googleのアカウントを持っていること。
- Googleの二段階認証の設定がなされていること。
    - まだの方→[Google 二段階認証設定をONにする方法](https://qiita.com/miriwo/items/0331e7241710fb4759fe)
- Googleのアプリケーション用パスワードを取得していること。
    - まだの方→[Google メールアプリケーション用のパスワードを取得する](https://qiita.com/miriwo/items/833d4189140831c5d039)
- 正常に動作しているRails６のアプリがあること。
- テストメールを送信できるアドレス(メールを送信しても迷惑にならないメールアドレス)を知っていること。
    - 二段階認証、アプリケーション用パスワードを取得しているアカウントのメールアドレスでもOK(自分にテストメールを送信して確認する)

## 作業期待値

- とりあえず難しい処理は無しにしてRails6アプリケーションからのメールが送信できるようにする。
- Rails6のアプリケーションから何かのトリガーを用いてGmailのメールサーバからテストメールを送信する。
- 継承やその他詳しい話はせずとにかくメールを送信できるようにする。
- メーラーの詳しい話や処理方法は下記の本のメールについての記載を確認することをオススメする。(アフィリエイトリンクでは無いので安心してください。)
    - [独習Ruby on Rails](https://www.amazon.co.jp/%E7%8B%AC%E7%BF%92Ruby-Rails-%E5%B0%8F%E9%A4%85-%E8%89%AF%E4%BB%8B/dp/4798160687/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E7%8B%AC%E7%BF%92+rails&qid=1579310886&sr=8-1)

## 作業の前にちょっと聞いてほしいこと

- 筆者はこのメールの実装に非常に時間がかかった。
- データベースの内容をメールに添付しようとしたり、宛先を複数指定したりしたためミスが多くあった。
- 余計なことは考えずとにかくテストメールを送信できることを最優先に作業をした方が良い気がする。

## 作業概要

1. メーラーの作成
1. 送信アドレス設定
1. メールサーバの設定
1. 送信トリガーの設定
1. テスト送信

## 作業詳細

1. メーラーの作成
    1. 下記コマンドを実行してメーラーを作成する。(notice greetingはクラス名とメソット名であるため任意のものでも構わない)

        ```terminal
        $ cd アプリ名フォルダ
        $ rails g mailer notice greeting
        ```

    1. 先のコマンドを実行するとメーラーが作成される。メール送信に必要なファイル群が作成されると思っていただきたい。
1. 送信アドレス設定
    1. 下記に存在する送信先を指定するファイル`notice_mailer.rb`をエディタで開く
        - アプリ名フォルダ/app/mailers
            - notice_mailer.rb
    1. ファイル`notice_mailer.rb`の下記の部分を下記のように修正してテストメールを送信する先のアドレスを設定する。

        ```ruby
        class NoticeMailer < ApplicationMailer

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

            mail to: "to@example.org"
          end
        end
        ```
        ↓修正

        ```ruby
        class NoticeMailer < ApplicationMailer

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

            mail to: "テストメール送信先アドレス"
          end
        end
        ```

1. メールサーバの設定
    1. 下記に存在する送信先を指定するファイル`development.rb`をエディタで開く
        - アプリ名フォルダ/config/environments
            - development.rb
    1. ファイル`development.rb`を下記のように修正してテストメールを送信するメールサーバを設定する。

        ```ruby
        Rails.application.configure do
          # Settings specified here will take precedence over those in config/application.rb.
        
          # In the development environment your application's code is reloaded on
          # every request. This slows down response time but is perfect for development
          # since you don't have to restart the web server when you make code changes.
          config.cache_classes = false
        
          # Do not eager load code on boot.
          config.eager_load = false
        
          # Show full error reports.
          config.consider_all_requests_local = true
        
          # Enable/disable caching. By default caching is disabled.
          # Run rails dev:cache to toggle caching.
          if Rails.root.join('tmp', 'caching-dev.txt').exist?
            config.action_controller.perform_caching = true
            config.action_controller.enable_fragment_cache_logging = true
        
            config.cache_store = :memory_store
            config.public_file_server.headers = {
              'Cache-Control' => "public, max-age=#{2.days.to_i}"
            }
          else
            config.action_controller.perform_caching = false
        
            config.cache_store = :null_store
          end
        
          # Store uploaded files on the local file system (see config/storage.yml for options).
          config.active_storage.service = :local
        
          # Don't care if the mailer can't send.
          config.action_mailer.raise_delivery_errors = false
        
          config.action_mailer.perform_caching = false
        
          # Print deprecation notices to the Rails logger.
          config.active_support.deprecation = :log
        
          # Raise an error on page load if there are pending migrations.
          config.active_record.migration_error = :page_load
        
          # Highlight code that triggered database queries in logs.
          config.active_record.verbose_query_logs = true
        
          # Debug mode disables concatenation and preprocessing of assets.
          # This option may cause significant delays in view rendering with a large
          # number of complex assets.
          config.assets.debug = true
        
          # Suppress logger output for asset requests.
          config.assets.quiet = true
        
          # Raises error for missing translations.
          # config.action_view.raise_on_missing_translations = true
        
          # Use an evented file watcher to asynchronously detect changes in source code,
          # routes, locales, etc. This feature depends on the listen gem.
          config.file_watcher = ActiveSupport::EventedFileUpdateChecker
        end
        ```
        ↓修正

        ```ruby
        Rails.application.configure do
          # Settings specified here will take precedence over those in config/application.rb.
        
          # In the development environment your application's code is reloaded on
          # every request. This slows down response time but is perfect for development
          # since you don't have to restart the web server when you make code changes.
          config.cache_classes = false
        
          # Do not eager load code on boot.
          config.eager_load = false
        
          # Show full error reports.
          config.consider_all_requests_local = true
        
          # Enable/disable caching. By default caching is disabled.
          # Run rails dev:cache to toggle caching.
          if Rails.root.join('tmp', 'caching-dev.txt').exist?
            config.action_controller.perform_caching = true
            config.action_controller.enable_fragment_cache_logging = true
        
            config.cache_store = :memory_store
            config.public_file_server.headers = {
              'Cache-Control' => "public, max-age=#{2.days.to_i}"
            }
          else
            config.action_controller.perform_caching = false
        
            config.cache_store = :null_store
          end
        
          # Store uploaded files on the local file system (see config/storage.yml for options).
          config.active_storage.service = :local
        
          # Don't care if the mailer can't send.
          # config.action_mailer.perform_caching = false
          config.action_mailer.raise_delivery_errors = true
          config.action_mailer.delivery_method = :smtp
          config.action_mailer.smtp_settings = {
            :enable_starttls_auto => true,
            :address => 'smtp.gmail.com',
            :port => '587',
            :domain => 'smtp.gmail.com',
            :authentication => 'plain',
            :user_name => '二段階認証設定、アプリケーションパスワードを取得したアカウントのメールアドレス',
            :password => 'アプリケーションパスワード(表示された時は4文字で区切られていたがスペース入れず連続して記載)'
          }
        
          # Print deprecation notices to the Rails logger.
          config.active_support.deprecation = :log
        
          # Raise an error on page load if there are pending migrations.
          config.active_record.migration_error = :page_load
        
          # Highlight code that triggered database queries in logs.
          config.active_record.verbose_query_logs = true
        
          # Debug mode disables concatenation and preprocessing of assets.
          # This option may cause significant delays in view rendering with a large
          # number of complex assets.
          config.assets.debug = true
        
          # Suppress logger output for asset requests.
          config.assets.quiet = true
        
          # Raises error for missing translations.
          # config.action_view.raise_on_missing_translations = true
        
          # Use an evented file watcher to asynchronously detect changes in source code,
          # routes, locales, etc. This feature depends on the listen gem.
          config.file_watcher = ActiveSupport::EventedFileUpdateChecker
        end
        ```

1. 送信トリガーの設定(ここからは各個人のプロダクトにより若干方法が異なるが、単純に送信のきっかけを作っているだけなので難しく考えないでほしい)
    1. 送信のトリガーとなる処理(以降、メールトリガーコードと呼ぶ)を任意のコントローラファイルに記載する。
    1. 任意のコントローラのアクション内に下記メールトリガーコードを記載する。
        - ※メーラーの作成時にクラス名、メソット名を独自の物にした人は若干異なるので注意
        - ※難しく考えず、下記の処理を記載してコントローラ内で実行してあげればメールが送信されると考えると記載箇所の応用が効きやすいかもしれない

        ```ruby
        # メーラークラス名.メール送信メソット名.送信メソット名
        NoticeMailer.greeting.deliver_now
        ```

    1. 前述の記載を任意のアクション内に記載するが、オススメは任意ページを表示するアクションに記載することである。
    1. 筆者は`http://localhost:3000/posts/index/:id`にアクセスした時にテストメールが送られるように設定した。
    1. 筆者のメールトリガーコードの記載例を下記に記載する。
        - `post_controller.rb`ファイルの`index`アクション内に下記を記載 

        ```ruby
        class PostsController < ApplicationController
          def index
            @posts = Post.where(user_id: @current_user.id)
        end
        ```
        ↓メールトリガーコードを記載

        ```ruby
        class PostsController < ApplicationController
          def index
            NoticeMailer.greeting.deliver_now
            @posts = Post.where(user_id: @current_user.id)
          end
        ```

1. テスト送信
    1. 下記コマンドを実行してアプリケーションを起動する。

        ```ruby
        $ cd アプリ名フォルダ
        $ rails s
        ```

    1. アプリケーション内でメールトリガーコードを記載したコントローラのアクションが実行されるようにページ遷移する。
    1. 筆者の場合`http://localhost:3000/posts/index/:id`にアクセスするとメールトリガーコードが実行されるため上記画面を表示した。

1. メール確認
    1. 下記のようなメールが送信先アドレスに届いていれば実装完了である。
    ![スクリーンショット 2020-01-18 11.10.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/10890e7e-60ec-96b2-bdef-d58138202078.png)

# Net::SMTPAuthenticationErrorのエラー

1. メールトリガーコードを記載したコントローラのアクションを実行したところエラー`Net::SMTPAuthenticationError`が発生した。
1. これは二段階認証を使用してGmailのをメールサーバを使用した場合にアプリケーションパスワードが一致していない時に出るエラーらしい。
    - 筆者のエラーメッセージ: 534-5.7.9 Application-specific password required. Learn more at 
1. ファイル`development.rb`の二段階認証を行なったアカウントのメールアドレス、パスワードが間違えていないかもう一度確認しよう。
1. 正常に設定されているなら、railsアプリを終了し10分~2０分ほど放置してから再トライしてみよう。
1. サーバとのやりとりに時間がかかっており、ファイル`development.rb`でパスワード設定直後だと接続がうまくいかない恐れがある。(筆者談、真意は不明)

# エラーは出てないがメールが来ない

1. ファイル`notice_mailer.rb`で指定している送信先のメールアドレスを入力していないかを確認してみよう。

# 付録

- 筆者の環境でメール作成の実績がある各ファイルの内容を下記に記載する。
- パスワードはダミーを入力する。
- 今回説明に出てきていないメーラー作成コマンドで作成されたそのほかのファイルの記載状況もまとめる。

- notice_mailer.rb

```ruby
class NoticeMailer < ApplicationMailer

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

    mail to: "shun.okawa@gmail.com",
         cc: "miriwo.rails@gmail.com"
  end
end
```

- development.rb

```ruby
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports.
  config.consider_all_requests_local = true

  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join('tmp', 'caching-dev.txt').exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true

    config.cache_store = :memory_store
    config.public_file_server.headers = {
      'Cache-Control' => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false

    config.cache_store = :null_store
  end

  # Store uploaded files on the local file system (see config/storage.yml for options).
  config.active_storage.service = :local

  # Don't care if the mailer can't send.
  # config.action_mailer.perform_caching = false
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :enable_starttls_auto => true,
    :address => 'smtp.gmail.com',
    :port => '587',
    :domain => 'smtp.gmail.com',
    :authentication => 'plain',
    :user_name => 'miriwo.rails@gmail.com',
    :password => 'smppyvjbfzhweyxx'
  }

  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log

  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load

  # Highlight code that triggered database queries in logs.
  config.active_record.verbose_query_logs = true

  # Debug mode disables concatenation and preprocessing of assets.
  # This option may cause significant delays in view rendering with a large
  # number of complex assets.
  config.assets.debug = true

  # Suppress logger output for asset requests.
  config.assets.quiet = true

  # Raises error for missing translations.
  # config.action_view.raise_on_missing_translations = true

  # Use an evented file watcher to asynchronously detect changes in source code,
  # routes, locales, etc. This feature depends on the listen gem.
  config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end
```

- posts_controller.rb

```ruby
class PostsController < ApplicationController
  def index
    NoticeMailer.greeting.deliver_now
    @posts = Post.where(user_id: @current_user.id)
  end

  def show
    @post = Post.find_by(id: params[:id])
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(content: params[:content], study_time: params[:'study_time'], today_study_time: params[:'study_time'], hash_tag: params[:hash_tag], user_id: @current_user.id)
    if @post.save
      # flash[:notice] = "保存完了"
      redirect_to("/posts/#{@post.id}")
    else
      # flash[:notice] = "保存失敗"
      render ("posts/new")
    end
  end

  def edit_form
    @post = Post.find_by(id: params[:id])
  end

  def edit
    @post = Post.find_by(id: params[:id])
    @post.content = params[:content]
    @post.hash_tag = params[:hash_tag]
    @post.save
    redirect_to("/posts/#{@post.id}")
  end

  def achievement
    @post = Post.find_by(id: params[:id])

  end

  def destroy
    @post = Post.find_by(id: params[:id])
    @post.destroy
    redirect_to("/posts/index/#{@post.user_id}")
  end

  def update
    @post = Post.find_by(id: params[:id])
    # 本当は下記見たいにしたいけどエラー出る。
    # 原因は@post.study_timeが数値でparams[:study_time]が文字列になってしまっているから
    @post.today_study_time = params[:study_time].to_f
    @post.save
    @post.study_time += @post.today_study_time
    @post.save
    redirect_to("/posts/#{@post.id}")
  end

  def tweet_content
    @post = Post.find_by(id: params[:id])
  end
end
```

- `アプリ名/app/mailers`のapplication_mailer.rb

```ruby
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end
```

- `アプリ名/app/views/notice_mailer`のgreeting.html.erb

```ruby
<h1>Notice#greeting</h1>

<p>
  <%= @greeting %>, find me in app/views/notice_mailer/greeting.html.erb
</p>
```

- `アプリ名/app/views/notice_mailer`のgreeting.text.erb

```
Notice#greeting

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

- `アプリ名/test/mailers`のnotice_mailer_test.rb

```ruby
require 'test_helper'

class NoticeMailerTest < ActionMailer::TestCase
  test "greeting" do
    mail = NoticeMailer.greeting
    assert_equal "Greeting", mail.subject
    assert_equal ["to@example.org"], mail.to
    assert_equal ["from@example.com"], mail.from
    assert_match "Hi", mail.body.encoded
  end

end
```

- `アプリ名/test/mailers/previews`のnotice_mailer_preview.rb

```ruby
# Preview all emails at http://localhost:3000/rails/mailers/notice_mailer
class NoticeMailerPreview < ActionMailer::Preview

  # Preview this email at http://localhost:3000/rails/mailers/notice_mailer/greeting
  def greeting
    NoticeMailer.greeting
  end

end
```

- Gemfile

```ruby
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.0'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.0'
# Use sqlite3 as the database for Active Record
# gem 'sqlite3', '~> 1.4'
# Use Puma as the app server
gem 'puma', '~> 3.12'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem 'bootstrap', '~> 4.1.1'
gem 'jquery-rails'

gem 'mysql2'

#gem 'bcrypt'
```