Next.js와 nodemailer를 사용해서 이메일 보내는 방법 feat. 문의하기 form 폼, Gmail 설정하기

next js에서 nodemailer를 사용해서 이메일을 보내는 방법과 문의하기 폼을 만드는 방법을 알아보고 Gmail 설정하는 방법을 알아보겠습니다.


Summary

웹사이트 제작시 간혹 문의하기 폼을 만들어야하는 경우가 있습니다. 이때 문의하기의 내용을 이메일로 받아볼 수 있도록 nodemailer를 사용해서 이메일을 보내는 방법을 알아보겠습니다.

비록 문의하기(contact us) 폼을 예시로 들었지만, nodemailer를 사용하면 회원가입 인증 이메일, 비밀번호 찾기 이메일 등 다양한 용도로 사용할 수 있습니다.

본 블로그에서는 Next.js와 nodemailer를 사용해서 gamil 이메일을 보내는 방법과 문의하기 폼을 만드는 방법을 알아보겠습니다.

이 글은 Next.js app router를 사용하고 server actionuseFormState useFormStatus를 사용합니다.

목차

  1. 폼 UI 만들기
  2. form action 서버 함수 만들기
  3. nodemailer 설정하기

1. 폼 UI 만들기

먼저 간단한 문의하기 폼을 만들어보겠습니다. 간단히 사용법을 알아보기 위해서 style은 최소화하였습니다.

components/Form.tsx
const Form = () => {
  return (
    <form>
      <div className="flex flex-col gap-4">
        <div>
          <label htmlFor="name">이름: </label>
          <input type="text" id="name" name="name" placeholder="이름" />
        </div>
        <div>
          <label htmlFor="email">이메일: </label>
          <input type="email" id="email" name="email" placeholder="이메일" />
        </div>
        <div>
          <label htmlFor="subject">문의 내용: </label>
          <textarea placeholder="문의 내용" id="subject" name="subject" />
        </div>
      </div>
      <button type="submit">전송</button>
    </form>
  );
};

간단히 폼을 만들었습니다. 각 label과 id, name등을 설정해줍니다. next.js 서버액션을 사용하기 위해 name은 필수입니다.

이제 폼을 만들었으니, 이제 nodemailer를 사용해서 이메일을 보내는 방법을 알아보겠습니다.


2. form action 서버 함수 만들기

Form에서 입력한 데이터를 서버로 전송하기 위해서는 서버 액션 함수를 만들어야합니다.

lib/action.ts
'use server';
export const sendEmail = async (prevState: any, formData: FormData) => {
  try {
    const { name, email, subject } = Object.fromEntries(formData);
 
    console.log(name, email, subject);
  } catch (error) {
    console.error(error);
  }
};

server action을 사용하기 위해서 파일 최상단에 'use server'를 추가해줍니다.

그리고 useFormState를 사용하기 위해 FormData를 사용합니다. Object.fromEntries(formData)를 사용해서 form data를 object로 변환합니다.

Object.fromEntries(formData)를 사용하지 않고 개별로 formData.get을 사용해도 됩니다.

lib/action.ts - 예시
const name = formData.get('name');
const email = formData.get('email');
const subject = formData.get('subject');

많은 양의 form data를 사용할 때는 Object.fromEntries(formData)를 사용하는 것이 편리합니다.

이제 다시 Form 컴포넌트로 돌아가서 useFormState를 사용해서 form data를 가져오고, sendEmail 함수를 호출하도록 수정해보겠습니다.

components/Form.tsx
'use client';
 
import { sendEmail } from '@/lib/action';
import { useFormState } from 'react-dom';
 
const Form = () => {
  const [actionState, formAction] = useFormState(sendEmail, null);
 
  return (
    <form action={formAction}>
      {/* ... */}
      {/* 기존 내용과 동일, 생략 */}
    </form>
  );
};
 
export default Form;

useFormState는 클라이언트 컴포넌트에서 사용할 수 있습니다. 'use client'를 파일 최상단에 추가해줍니다.

useFormState는 첫 번째 인자로 서버 액션 함수를, 두 번째 인자로 초기 상태를 받습니다.

useFormState의 자세한 내용은 `React 공식문서 - useFormState` 를 참고해주세요.

form action에 formAction을 추가해주면, form이 submit 될 때 sendEmail 함수가 호출됩니다.

이제 Form 데이터를 입력하고 전송 버튼을 누르면, sendEmail 함수가 호출되어 Form 데이터를 console에 출력합니다.

예시로 이름은 홍길동, 이메일은 gildong@gmail.com, 문의 내용은 '안녕하세요 문의 드립니다'를 입력하고 전송버튼을 클릭하였습니다. 이제 터미널의 console을 확인해 봅니다.

console 예시
홍길동 gildong@gmail.com 안녕하세요 문의 드립니다

터미널 콘솔에서 위와 같이 입력한 내용 Form 데이터가 출력되면 성공입니다.

이제 입력값을 받았으니 이제 nodemailer를 사용해서 이메일을 보내는 방법을 알아보겠습니다.


3. nodemailer 설정하기

nodemailer를 사용하기 위해서는 먼저 nodemailer를 설치해야합니다.

npm install nodemailer

typescript를 사용하고 있다면, @types/nodemailer도 설치해줍니다.

npm i --save-dev @types/nodemailer

nodemailer를 설치하였다면, nodemailer를 사용하기 위한 설정을 추가해줍니다.

lib/action.ts
import nodemailer from 'nodemailer';
 
const transporter = nodemailer.createTransport({
  host: 'smtp.gmail.com',
  port: 465,
  secure: true,
  auth: {
    user: '',
    pass: '',
  },
});
 
export const sendEmail = async (prevState: any, formData: FormData) => {
  // ... 이전 내용 생략
};

nodemailer를 사용하기 위해서는 transporter를 생성해야합니다. transporter는 이메일을 보내는 역할을 합니다.

보내는 이메일을 사용하기 위해 gmail을 사용하도록 하겠습니다. gmail대신 다른 이메일도 사용 가능하나, nodemailer 사용법을 설명하기 위한 방법이기에 gmail을 사용하도록 하겠습니다.

3.1. Gmail 설정하기

gmail을 이미 가입했다고 가정하고, gmail을 사용하기 위해서는 gmail 설정을 해주어야합니다.

Gmail의 계정 관리로 이동합니다.

gmail 계정관리 이동

gmail 계정관리 이동

계정 관리에서 보안으로 이동합니다.

Gamil을 연동하기 위해서는 앱 비밀번호가 필요합니다.

앱 비밀번호를 발급 받으려면 반드시 2단계 인증을 설정해야합니다.

2단계 인증을 하지않은 경우는 앱 비밀번호를 받을 수 없음.

gmail 2단계 인증

gmail 2단계 인증

2단계 인증을 클릭후 2단계 인증을 활성화를 한 다음 2단계 인증할 핸드폰 번호 및 패스키 설정을 합니다.

저는 간편하게 사용하기위해 패스키 인증을 사용하였습니다.

이제 2단계 인증 및 2단계 인증 비밀번호를 설정하였다면, 앱 비밀번호를 발급 받을 수 있습니다.

검색창에서 앱 비밀번호를 검색합니다.

gmail 앱 비밀번호 검색

gmail 앱 비밀번호 검색

앱 비밀번호를 클릭합니다.

페이지 이동하면 앱 이름을 설정한 후 만들기 버튼을 클릭하면 앱 비밀번호가 생성됩니다.

앱 비밀번호는 생성하면 한 번만 보여지며, 다시 보려면 새로 생성해야합니다.

생성된 키를 잘 보관하고, 환경변수에 저장해 둡니다.

.env.local
GMAIL_APP_KEY = '생성된 앱 비밀번호';
GMAIL_USER = '이메일 주소';

.env를 따로 설정해도 됩니다. 단, .env는 git에 올리지 않도록 주의해야합니다.

기본적으로 .env.local은 gitignore에 추가되어 있습니다.

gitignore에 .env를 추가해주세요.

.gitignore
.env

이제 transporter를 설정해줍니다. action.ts 파일을 수정해줍니다.

lib/action.ts
import nodemailer from 'nodemailer';
 
const transporter = nodemailer.createTransport({
  host: 'smtp.gmail.com',
  port: 465,
  secure: true,
  auth: {
    user: process.env.GMAIL_USER,
    pass: process.env.GMAIL_APP_KEY,
  },
});

이제 transporter를 설정하였습니다. 이제 transporter를 사용해서 이메일을 보내는 코드를 작성해보겠습니다.

lib/action.ts
export const sendEmail = async (prevState: any, formData: FormData) => {
  try {
    const { name, email, subject } = Object.fromEntries(formData);
 
    await transporter.sendMail({
      from: process.env.GMAIL_USER, // 보내는 이메일
      to: '받는 이메일 주소', // 받는 이메일
      subject: `문의하기: ${name}(${email})`,
      html: `<p>${subject}</p>`,
    });
 
    console.log('이메일 전송 성공');
    return { message: '이메일 전송 성공' };
  } catch (error) {
    console.error(error);
  }
};

이제 transporter를 사용해서 이메일을 보내는 코드를 작성하였습니다.

  • from - 보내는 이메일 주소
  • to - 받는 이메일 주소
  • subject - 이메일 제목
  • html - 이메일 내용

from은 보내는 이메일 주소를 입력합니다. 보내는 이메일 주소는 gmail 계정으로 설정한 이메일 주소를 입력합니다.

to는 받는 이메일 주소를 입력합니다. 받는 이메일 주소는 본인의 이메일 주소를 입력합니다.

만약 받는 주소를 여러곳에 보내고 싶다면 배열로 받는 이메일 주소를 입력하면 됩니다.

lib/action.ts
// - 여러 이메일 주소로 보내기
to: ['이메일 주소1', '이메일 주소2'],

subject는 이메일 제목을 입력합니다. 이메일 제목은 문의하기: 이름(이메일)로 설정하였습니다.

html은 이메일 내용입니다. html 형식으로, 받는 이메일에서 html 형식으로 보여집니다. 각 HTML 태그를 사용하여 이메일 내용을 작성합니다.

lib/action.ts
html: `<h1>문의 내용</h1>
      <p>${subject}</p>
    `,

이제 Form 데이터를 입력하고 전송 버튼을 누르면, sendEmail 함수가 호출되어 Form 데이터를 이메일로 보내는 코드를 작성하였습니다.

이제 Form 데이터를 입력하고 전송 버튼을 누르면, 이메일이 보내질 것입니다.

받는 이를 나의 이메일을 추가하여 이메일을 받아 봅시다. 이메일이 잘 도착하면 성공입니다.

받은 이메일

받은 이메일

이메일이 잘 도착하였습니다.

이제 위 코드에서 유효성을 추가해 줘야합니다. 만약, Form 데이터를 입력하지 않았을 경우, 에러 및 빈 값이 전송되지 않도록 유효성을 추가해주어야합니다.

lib/action.ts
export const sendEmail = async (prevState: any, formData: FormData) => {
  try {
    const { name, email, subject } = Object.fromEntries(formData);
 
    if (!name || !email || !subject) {
      return { message: '모든 필드를 입력해주세요' };
    }
 
    await transporter.sendMail({
      from: process.env.GMAIL_USER, // 보내는 이메일
      to: '받는 이메일 주소', // 받는 이메일
      subject: `문의하기: ${name}(${email})`,
      html: `<p>${subject}</p>`,
    });
 
    console.log('이메일 전송 성공');
    return { message: '이메일 전송 성공' };
  } catch (error) {
    console.error(error);
  }
};

이제 Form 데이터를 입력하지 않고 전송 버튼을 누르면, 모든 필드를 입력해주세요 메시지가 출력됩니다.

server action에서 return을 하면, 해당 메시지가 클라이언트로 전달됩니다.

클라이언트는 메세지를 이전에 작성하였던 useFormState를 사용해서 출력할 수 있습니다.

useFormState에서 formAction은 전송하는 함수로 사용되고 첫번째 actionState는 서버에서 반환하는 메시지를 받습니다.

components/Form.tsx
const Form = () => {
  const [actionState, formAction] = useFormState(sendEmail, null);
 
  console.log(actionState);
 
  return (
    // ... 생략
  );
};

console.log(actionState)를 추가하면, 서버에서 반환하는 메시지를 확인할 수 있습니다.

{message: '모든 필드를 입력해주세요'}

반환되는 message를 사용하여 Form 하단에 메시지를 출력할 수 있습니다.

이제 Next.js와 nodemailer를 사용해서 이메일을 보내는 방법과 문의하기 폼을 만드는 방법을 알아보았습니다.

이제 여러분의 웹사이트에 문의하기 폼을 추가하여 이메일을 받아보세요.

이상으로 Next.js와 nodemailer, 그리고 Gmail을 사용한 이메일보내기, Next js에서 useFormState를 사용한 서버액션에서 Form 데이터를 받는 방법을 알아보았습니다.

다음에는 Form에서 받은 이메일을 더욱 상세한 유효성을 검사하는 방법을 알아보겠습니다.


관련 태그