현재 찾아볼 수 있는 nodemailer 자료들은, 거의 다 동기 처리에 대한 자료이고 nodemailer와 RabbitMQ를 연동하여 mailing 서비스를 비동기 처리하는 방법에 대한 자료는 nodemailer에서 제공하는 예제 뿐인데, 약간 예전 자료인데에다가 예제가 로컬에서는 gsmtp 또는 nsmtp를 이용해서 잘 돌아가지 않았다.
그래서 해당 예제를 잡고 끙끙대다가, github에서 다른 사람이 nodemailer와 RabbitMQ를 이용해서 mailing 서비스를 간단히 구현한 코드를 보고 따라할 수 있었다.
해당 코드는 sql에서 note들을 모두 검색해서 첨부파일로 비동기메일링하는 코드이며, 변형해서 사용했다.
구현은 다음과 같다.
우리 서비스는 두가지 메일을 통한 인증 서비스를 진행한다. 한개는 학교 메일 인증이고, 한개는 비밀번호 재설정을 위한 인증인데, 해당하는 type에 따라 file을 다르게 render하도록 설정했다. authCode는 res로도 보내주어야 하기 때문에 router에서 핸들링해서 넘겨준다.
const amqp = require("amqplib");
const MailSender = require("./MailSender");
const Listener = require("./Listener");
const init = async () => {
const mailSender = new MailSender();
const listener = new Listener(mailSender);
const connection = await amqp.connect(process.env.RABBITMQ_SERVER);
const channel = await connection.createChannel();
await channel.assertQueue("export:mail", {
durable: true,
});
channel.consume("export:mail", listener.listen, { noAck: true });
};
init();
class Listener {
constructor(mailSender) {
this._mailSender = mailSender;
this.listen = this.listen.bind(this);
}
async listen(message) {
try {
const { targetEmail, type, authCode } = message;
const result = await this._mailSender.sendEmail(
targetEmail,
type,
authCode
);
console.log(result);
} catch (error) {
console.error(error);
}
}
}
module.exports = Listener;
const nodemailer = require("nodemailer");
const ejs = require("ejs");
const path = require("path");
const appDir = path.dirname(require.main.filename);
class MailSender {
constructor() {
this._transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
user: process.env.NODEMAILER_USER,
pass: process.env.NODEMAILER_PASS,
},
});
}
sendEmail(targetEmail, type, authCode) {
let message;
let emailTemplete;
if (type == "auth") {
ejs.renderFile(
appDir + "/template/authmail.ejs",
{ authCode },
function (err, data) {
if (err) {
console.log(err);
}
emailTemplete = data;
}
);
message = {
from: `UFO`,
to: targetEmail,
subject: "회원가입을 위한 인증번호를 입력해주세요.",
html: emailTemplete,
};
} //다른 타입에 대한 코드 생략
return this._transporter.sendMail(message);
}
}
module.exports = MailSender;
router.post("/email", async (req, res) => {
try {
const { school_email } = req.body;
//school email에 대한 예외 처리 생략
const authCode = Math.random().toString().substr(2, 6);
const mailSender = new MailSender();
const listener = new Listener(mailSender);
listener.listen({
targetEmail: school_email,
type: "auth",
authCode,
});
res.status(200).send({ authCode });
} catch (err) {
res.status(400).send({
ok: false,
message: err + " : email 전송 실패!",
});
}
});
추가로 해결해야될 부분은, 메일 전송이 실패했을 때 어떤식의 에러가 나는지 테스트해보고 핸들링해야한다.