Giới thiệu

Ở bài viết này chúng ta sẽ làm một ứng dụng đơn giản để trình diễn việc gửi email với ActionMailer, ActionMailer Preview và thông qua một nhà cung cấp dịch vụ bên thứ ba như Gmail hoặc Mailgun. Chúng ta sẽ demo cách sử dụng Active job để gửi email cùng với background processor (tiến trình chạy ngầm).

 

 

Gửi mail sử dụng ActionMailer và Gmail

Bây giờ chúng ta sẽ xây dựng một ứng dụng rails để gửi email đến user khi một user mới được tạo

$ rails new new_app_name
$ rails g scaffold user name:string email:string
$ rake db:migrate

Bây giờ chúng ta đã có một ứng dụng cơ bản, chúng ta sẽ sử dụng ActionMailer. Việc tạo mới Mailer cũng tương tự như bất kỳ tạo mới nào trên rails.

$ rails g mailer example_mailer
create  app/mailers/example_mailer.rb
invoke  erb
create    app/views/example_mailer
invoke  test_unit
create    test/mailers/example_mailer_test.rb
create    test/mailers/previews/example_mailer_preview.rb

Ứng dụng mà chúng ta đang sử dụng là bản Rails 4.2.0.beta4 do đó khi tạo mailer thì ứng dụng có tự động tạo ra một file test là example_mailer_preview.rb đặt trong thư mục previews, để sau này có thể được sử dụng

Trong file app/mailers/example_mailer.rb

class ExampleMailer < ActionMailer::Base
  default from: "from@example.com"
end

Bây giờ chúng ta sẽ viết ra một phương thức và customized email. Trước tiên, bạn nên thay đổi địa chỉ gửi mail mặc định từ from@example.com thành địa chỉ mail mà bạn muốn gửi đi (hay còn gọi là sender’s address).

class ExampleMailer < ActionMailer::Base
  default from: "from@example.com"

  def sample_email user
    @user = user
    mail to: @user.email, subject: "Sample Email"
  end
end

Phương thức sample_email lấy các parameter của user và gửi email sử dụng phương thức mail để gửi đến địa chỉ email của user. Trong trường hợp bạn muốn biết thêm về các tính năng như tập tin đính kèm hay là gửi cho nhiều email, bạn có thể xem hướng dẫn trong tài liệu tham khảo ở link bên dưới. Bây giờ chúng ta sẽ viết nội dung mail mà bạn muốn gửi đến cho user, tất cả đều được lưu trong thư mục app/views/example_mailer. Bạn tạo ra một file tên là sample_email.html.erb có định dạng chuẩn HTML

Trong file app/views/example_mailer/sample_email.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
  </head>
  <body>
    <h1>Hi <%= @user.name %></h1>
    <p>
      Sample mail sent using smtp.
    </p>
  </body>
</html>

Nếu chúng ta cần tạo ra dạng văn bản(cách dòng hoặc thụt vào đầu dòng) cho email thì bạn phải tạo ra file sample_email.text.erb thay cho file sample_email.html.erb trong thư mục app/views/example_mailer

Trong file app/views/example_mailer/sample_email.text.erb

Hi <%= @user.name %>
Sample mail sent using smtp.

Trong môi trường test, chúng ta sử dụng ActionMailer Preview để test ứng dụng của chúng ta. Chúng ta sẽ sử dụng test/mailers/previews/example_mailer_preview.rb để tạo ra các mailer. Chúng ta có thể gọi đến bất kỳ user nào (trong trường hợp này chỉ gọi đến user đầu tiên) để test các email.

# Test tất cả các email tại http://localhost:3000/rails/mailers/example_mailer
class ExampleMailerPreview < ActionMailer::Preview
  def sample_mail_preview
    ExampleMailer.sample_email(User.first)
  end
end

Khi bạn truy cập địa chỉ http://localhost:3000/rails/mailers/example_mailer/sample_mail_preview bạn sẽ nhìn thấy preview của email. Theo mặc định email previews sẽ được đặt trong test/mailers/previews. Bạn có thể thay đổi điều này bằng cách thiết lập một đường dẫn khác trong /config/environments/development.rb. Chỉ cần thiết lập config.action_mailer.preview_path đến đường dẫn mong muốn và thêm file preview vào vị trí tương ứng.

Gửi mail sử dụng ActionMailer và Gmail

Theo mặc định rails sẽ gửi email thông qua SMTP. Do đó chúng ta sẽ cấu hình SMTP trong cài đặ môi trường /config/environments/production.rb. Đầu tiên chúng ta sẽ nhìn thấy cấu hình này là để chạy gửi email của Gmail. Trước khi chúng ta lưu các thông tin nhạy cảm này như username và password trong biến mội trường. Chúng ta cần sử dụng gem figaro.

Trong file /config/application.yml

gmail_username: 'username@gmail.com'
gmail_password: 'Gmail password'

Trong file /config/environments/production.rb

config.action_mailer.delivery_method = :smtp
# SMTP settings for gmail
config.action_mailer.smtp_settings = {
 :address              => "smtp.gmail.com",
 :port                 => 587,
 :user_name            => ENV['gmail_username'],
 :password             => ENV['gmail_password'],
 :authentication       => "plain",
:enable_starttls_auto => true
}

Bây giờ chúng ta sẽ chỉnh sửa UsersController để gọi đến sự kiện gửi email cho user. Chúng ta cần thêm vào ExampleMailer.sample_email(@user).deliver cho phương thức tạo mới trong app/controllers/users_controller.rb. Phương thức tạo mới trong users_controller.rb như sau:

def create
  @user = User.new(user_params)

  respond_to do |format|
    if @user.save

      # Sends email to user when user is created.
      ExampleMailer.sample_email(@user).deliver

      format.html { redirect_to @user, notice: 'User was successfully created.' }
      format.json { render :show, status: :created, location: @user }
    else
      format.html { render :new }
      format.json { render json: @user.errors, status: :unprocessable_entity }
    end
  end
end

Khi một user mới được tạo ra, chúng ta sẽ gửi một email thông qua phương thức sample_email trong mailer ExampleMailer.

Gửi email cùng với Background Processor thông qua Active Job

Khi bạn kiểm tra các ứng dụng, bạn sẽ thấy rằng sẽ mất rất nhiều thời gian (lâu hơn so với bình thường) để tạo ra một user mới. Chuyện này xảy ra bởi vì chúng ta phải gọi đến một API bên ngoài để gửi email. Việc này sẽ trở thành vấn đề lớn nếu cùng một lúc bạn gửi đi nhiều email hoặc gửi email đến nhiều người dùng. Vấn đề này sẽ được dễ dàng giải quyết nếu bạn bỏ các email gửi đi vào background jobs. Trong ứng dụng của chúng ta, chúng ta sẽ sử dụng Active Jobs và Delayed Job để gửi những email trong background.

Active Jobs là một bộ chuyển đổi cung cấp giao diện tổng quát cho các xử lý background như Resque, Delayed Job, Sidekiq,..vv. Chú ý rằng sử dụng Active Job bạn sẽ cần phiên bản Rails 4.2 trở lên.

$ rails g job send_email
  invoke  test_unit
  create    test/jobs/send_email_job_test.rb
  create  app/jobs/send_email_job.rb

Bây giờ chúng ta sẽ viết ra job để thực hiện bởi workers. Active Job được tích hợp với ActionMailer do đó dễ dàng để gửi các email bất đồng bộ. Đối với việc gửi email qua Active Job chúng ta sử dụng phương thức deliver_later.

Trong file /app/jobs/send_email_job.rb

class SendEmailJob < ActiveJob::Base
  queue_as :default

  def perform user
    @user = user
    ExampleMailer.sample_email(@user).deliver_later
  end
end

Bây giờ chúng ta sẽ sửa đổi trong phương thức create khi tạo mới user. Thay vì việc gửi email luôn khi tạo user, chúng ta sẽ đưa email vào hàng đợi(enqueue) và gọi đến hàm perform sau đó.

Trong file app/controllers/users_controller.rb


def create
  ...
  SendEmailJob.set(wait: 20.seconds).perform_later(@user)
  ....
end

Bây giờ chúng ta cần cấu hình backend cho tiến trình background của chúng ta. Chúng ta lựa chọn delayed_jobs để quản lý backend nhưng bạn cũng có thể lựa chọn backend của bạn nếu bạn muốn. Active Job có nhiều built-in adapter cho nhiều hàng đợi của backend.

Trong file Gemfile

gem 'delayed_job_active_record'
$ bundle
$ rails generate delayed_job:active_record
$ rake db:migrate

Thiết lập hàng đợi backend cho môi trường production

Trong file /config/environments/production.rb

config.active_job.queue_adapter = :delayed_job

Bây giờ tất cả mọi thứ đã được cấu hình, để test cho ứng dụng bạn vào tạo mới user. Một job mới sẽ được thêm vào hàng đợi và bạn sẽ nhận thấy thời gian cần thiết để tạo mới một user đã giảm đáng kể. Bạn bắt đầu chạy các job trong hàng đợi bằng lệnh sau:

$ bundle exec rake jobs:work