無論是驗證信箱、重設密碼、寄送電子報、更新服務條款、...,即使再小的 Real World App 都必須要考量到寄信服務。如果自己架 Mail Server 明顯過於克難,現在通常都是直接採用第三方服務,例如:Sendgrid,但是小弟沒有 $$ 玩不起這樣的服務,所以在 Boilerplate 中使用的是折衷作法,搭配 Nodemailer 這個 Library 利用 Gmail 來發信。
Nodemailer 是一個 Plugin-Based 的發信 Library,目前總共支援 36 種服務,包括 Gmail、Sendgrid、Amazon 的 SES。
在 Boilerplate 中我們把 Nodemailer 的原生寫法包裝成 nodemailerAPI
,保留日後擴充的彈性,目前只有 sendMail
這個 Method,使用方式如同我們呼叫 API 的一慣作法,回傳值是一個 Promise:
import Errors from '../../common/constants/Errors';
import nodemailerAPI from '../api/nodemailer';
export default {
// ...
sendResetPasswordLink(req, res) {
let { user } = req;
let token = user.toResetPasswordToken();
nodemailerAPI()
.sendMail({ /* nodemailer options */ })
.catch((err) => {
res.errors([Errors.SEND_EMAIL_FAIL]);
throw err;
})
.then((info) => {
res.json({
email: info.envelope,
});
});
},
};
基於成本考量,我們目前是使用免費的 Gmail 服務,讀者可以依照自己喜好修改 nodemailerAPI
,套用自己熟悉的服務。
另外,使用 Gmail 要記得申請應用程式專用的密碼,否則當你的 App 丟到 Travis 跑測試,或是部署到 Heroku 正式上線時,很可能被 Google 誤判為有人從美國入侵你的帳號。應用程式密碼的申請需要先啟用兩階段驗證,接著才能申請,完整教學請看 Boilerplate 的 README#Gmail
Nodemailer 的 Options 可以傳入 Html 作為信件內容,但是我們依然是運行在 MVC 的架構下,所以其實可以將 Html Option 抽取成獨立的元件,再呼叫 React-Dom 的 renderToString
產生 Html 即可:
import React from 'react';
import { renderToString } from 'react-dom/server';
import ResetPasswordMail from '../components/ResetPasswordMail';
nodemailerAPI()
.sendMail({
to: user.email.value,
subject: 'Reset Password Request',
html: renderToString(
<ResetPasswordMail
requestedAt={new Date()}
token={token}
/>
),
})