개인 포트폴리오 웹페이지를 만들어보면서 contact페이지에 사용자가 입력하는 내용을 나의 이메일로 전달받을 수 있는 방법에 대해 찾아보았다.
검색해보니 Nodemailer를 이용하여 메일전송 기능을 구현할 수 있다는 것을 알게되었다.
Nodemailer 란?
Node.js 기반 모듈로 Email Engine에 등록된 이메일 계정을 활용하여 이메일을 송수신 할 수 있도록 해준다.
npm i nodemailer
yarn add nodemailer
기본세팅
Next.js
와 Typescript
로 환경구축react-hook-form
을 활용하여 간단한 유효성 검사(한참 헤매다가 겨우 설정했다)
이렇게 앱 비밀번호 1개가 뜨면 생성된 것이다!
NEXT_PUBLIC_AUTH_USER=abc123@gmail.com
NEXT_PUBLIC_AUTH_PASS=abcdef
기능구현
// components/Contact.tsx
const {
register,
handleSubmit,
getValues,
reset,
formState: { errors },
} = useForm<Inputs>({ mode: "onChange" });
const onSubmitHandler = async () => {
const formData = new FormData();
formData.append("name", getValues().name);
formData.append("email", getValues().email);
formData.append("title", getValues().title);
formData.append("content", getValues().content);
try {
const response = await fetch("/api/contact", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`response status: ${response.status}`);
}
await response.json();
toast.success("이메일이 성공적으로 전송되었습니다");
reset();
} catch (error) {
console.error(error);
toast.error("이메일 전송에 실패하였습니다");
}
};
react-hook-form
을 함께 사용해주었기 때문에 이름, 이메일, 제목, 내용 입력값을 getValues()
로 가져와서 formData.append()
메서드를 사용하여 FormData에 담아주었다.
그리고 fetch() 함수를 사용하여 지정된 URL(/api/contact)에 POST 요청을 보내고 성공과 실패여부에 따라 알림이 표시될 수 있도록 하였다.
// contact/route.ts
import { NextResponse, NextRequest } from "next/server";
const nodemailer = require("nodemailer");
export const POST = async (request: NextRequest) => {
try {
const username = process.env.NEXT_PUBLIC_USERNAME;
const password = process.env.NEXT_PUBLIC_AUTH_PASS;
const authEmail = process.env.NEXT_PUBLIC_AUTH_USER;
if (!username || !password || !authEmail) {
throw new Error("정보에 오류가 발생하였습니다");
}
const formData = await request.formData();
const name = formData.get("name");
const email = formData.get("email");
const title = formData.get("title");
const content = formData.get("content");
const transporter = nodemailer.createTransport({
service: "gmail",
host: "smtp.gmail.com",
secure: false,
port: 587,
auth: {
user: authEmail,
pass: password,
},
});
const mailOptions = {
from: username,
to: authEmail,
subject: `[Portfolio] ${title}`,
html: `
<p>이름/소속: ${name} </p>
<p>이메일: ${email} </p>
<p>문의 내용: ${content} </p>
`,
};
await transporter.sendMail(mailOptions);
return NextResponse.json({ message: "Success" }, { status: 200 });
} catch (error) {
console.log(error);
NextResponse.json({ message: "fail" }, { status: 500 });
}
};
전달받은 데이터를 처리해주는 부분인데 먼저 POST 함수로 클라이언트로부터의 요청(request) 객체를 매개변수로 받는다.
그리고 아까 전달한 입력값들을 각각 변수에 담아준 후에 nodemailer.createTransport
를 사용하여 Gmail 서버에 연결해주었다.
Gmail을 사용하여 이메일을 보내기 위해 Gmail SMTP 서버를 설정하였는데 이 부분은 참고자료에서 많이 도움받았다.
마지막으로 subject에는 메일 제목을 어떻게 설정할지 정해주고 html로 메일 내용을 담아주었다.
transporter.sendMai
로 메일을 전송해주고 성공과 실패여부 처리!
참고
Nodemailer 문서
블로그1
블로그2
여기는 깃허브로 전체 코드도 참고할 수 있어서 아주 도움이 많이 되었다
트러블슈팅
작업하면서 계속 null값으로 메일전송이 되는 문제를 겪었었다.
그때 작성했던 코드는 아래와 같았다.
const onSubmitHandler: SubmitHandler<Inputs> = async (
event: React.FormEvent<HTMLFormElement>
) => {
const formData = new FormData(event.currentTarget);
hook-form을 사용하였기 때문에 currentTarget 으로는 값을 받아오지 못했던 것이다.
따라서 getValues()
라는 hook-form의 메서드를 사용하여 위에 기록한 코드로 수정하여 오류를 해결할 수 있었다.
회고
Nodemailer 라는 기능을 처음 구현해보면서 허우적거리기도 하였지만 알아두면 잘 사용한 기능이라고 생각이 되었다.
중요하다고 생각된 과정은 Gmail의 앱 비밀번호를 설정하는 것과 꼭 환경변수로!! 처리해주는 부분, 그리고 전송실패시에 대한 오류처리인 것 같다.