iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 9
0
Modern Web

關於 Ruby on Rails,我想說的是系列 第 18

[Day 18] 使用 ApplicationMailer 寄Email

  • 分享至 

  • xImage
  •  

Email 寄送是現在網站是必備功能,註冊時寄,週年慶寄,雙11寄,心情好吃飽飯後寄。無怪雖然各種網路行銷,FB,GA,GTM,Line@... 一直推陳出新,Email 行銷依然活躍在舞台上。
使用 Rails 寄Email是很簡單的任務,不用額外安裝Gem,只要透過原生的ApplicationMailer就可以辦到。

ApplicationMailer

假設信都是從電商平台的後台設定並寄出的,我要產生一個AdminMailerclass專門用來寄信,使用 Rails 內建的產生器g mailer,建立寄信功能需要的基本架構:

bin/rails g mailer Admin
# => 產生四個檔案或資料夾: 
create app/mailers/admin_mailer.rb
invoke erb
create   app/views/admin_mailer
invoke test_unit
create   test/mailers/admin_mailer_test.rb
create   test/mailers/previews/admin_mailer_preview.rb

先不看test_unit(測試),來看:app/mailers/admin_mailer.rb

class AdminMailer < ApplicationMailer
end

g mailer後面的參數首字大寫加上Mailer就成為AdminMailer class,它繼承自ApplicationMailerApplicationMailer的內容就在旁邊的檔案app/mailers/application_mailer.rb裡:

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

default:設定寄件人,預設是from@example.com,我修改成

 default from: 'max@example.com'

layout 'mailer'則是說email用的樣板,使用 app/views/layouts/mailer.html.erb,內容如下:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
      /* Email styles need to be inline */
    </style>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

有沒有發現跟平常的view layout很像,然後剛才的AdminMailer結構又跟controller很像。可以說 Mailer 和 Controller 非常類似。方法都叫做"動作",用 View 來 render 信件內容。但 Controller 通常是產生 HTML 回給客戶端;然而 Mailer 則是建立透過 email 寄出的信件(message)。

Controller vs. Mailer:

內容 Controller Mailer
類別 UsersController AdminMailer
父類別 ApplicationController ApplicationMailer
檔案位置 app/contollers/users_controller.rb app/mailers/admin_mailer.rb
Layout app/views/layouts/application.html.erb app/views/layouts/mailer.html.erb
View app/views/users/*.erb /app/views/admin_mailer/*.erb

再來跟平常對待controller一樣,定義AdminMailer的action:notify_customer。我接收了product實體,並且附加了一個檔案,最後用mail寄出email。

class AdminMailer < ApplicationMailer
  def notify_customer(product)
    @product = product
    attachments['Ruby 考試.jpg'] = File.read('public/Ruby 考試.jpg')
    mail(to: 'hchs200771@gmail.com', subject: '寄信測試')
  end
end

mail這個動作有幾個參數可以使用

  • to:收件人email
  • subject:信件主旨
  • cc:副本
  • bcc:密件副本
  • body: 信件內容

如果想要附加檔案,也只要像我這樣把檔名作為attachments的key,內容用之前教的File.read(path)把內容讀取出來放進attachments就可以了。

信件內容可以用body參數,ex.

mail(to: 'a@b.com', body: "<html><body>Hi there</body></html>")

但這方法不適合複雜的文件內容,我推薦像MVC裡面的view一樣,新增跟剛才AdminMaileracion 同名的notify_customer.html.erb的檔案,放在app/views/admin_mailer資料夾底下。

新增app/views/admin_mailer/notify_customer.html.erb成功之後,就可以開始加入信件內容。

<h1>您好,</h1>
<p>您的購買明細如下:
  <% @products.each do |product|%>
    <ul>
      <li>商品名稱:<%= product.title %></li>
      <li>商品價格:<%= product.price %></li>
    </ul>
  <% end %>
</p>
<p>Max Ithome 敬上</p>

我想在訂單建立成功時,寄信給客戶
可以在Order Model 的after_create callback來執行
編輯app/models/order.rb

class Order < ApplicationRecord
  after_create { AdminMailer.notify_customer(products).deliver_now }
  has_many :products
end

有兩個點要注意

1.剛才宣告notify_customer實體方法,但是ApplicationMailer會自動把它轉換成類別方法
2.AdminMailer.notify_customer(products)還要使用deliver_now才真的把信寄出去

到此算告一段落了,嘗試在console建立第一筆訂單

Order.create(name: '#1', products: Product.all)

# Server就開始寄信了
Rendering admin_mailer/notify_customer.html.erb within layouts/mailer
Rendered admin_mailer/notify_customer.html.erb within layouts/mailer (0.1ms)
AdminMailer#notify_customer: processed outbound mail in 21.4ms

不過現在也只是在本地端看server印出信件內容自嗨,還沒辦法真正寄email到信箱。

使用 mailgun 寄信

使用mailgun提供的第三方寄信服務,一個月有一萬封的額度。
步驟很簡單:
1.先註冊帳號
2.到domain 去,選擇SMTP

可以看到portUsernameDefault passwordSMTP hostname
把這些資料填入config/environments/development.rb就可以了

# 加上這段 SMTP 設定
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address:              'smtp.mailgun.org',
    port:                 587,
    domain:               'facebook.com',
    user_name:            'postmaster@sandbox17c.....599c.mailgun.org',
    password:             '9d3ea4fd107b1f.....00bc6',
    authentication:       'plain',
    enable_starttls_auto: true
  }

domain只要是合法的url都可以,這裡我是借用facebook.com

最後在console建立一個訂單

Order.create!(name: '#2', products: Product.all)

成功了⁽⁽٩(๑˃̶͈̀ ᗨ ˂̶͈́)۶⁾⁾


上一篇
[Day 17] 深入了解includes 原理
下一篇
[Day 19] 使用 ActiveJob & Sidekiq 背景執行工作
系列文
關於 Ruby on Rails,我想說的是23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言