Next.js와 nodemailer 이메일 파일전송 구현하는 방법 feat. server action

nodemailer를 사용하고 Next.js에서 useFormState를 사용하여 이메일 파일전송 구현하는 방법을 알아보겠습니다.


Summary

이번 포스팅에서는 Next.js와 nodemailer를 사용하여 이메일 파일전송을 구현하는 방법을 알아보겠습니다.

이전에 nodemailer를 사용하는 방법을 포스팅한 적이 있습니다. nodemailer를 사용하여 이메일을 보내는 방법은 `Next.js와 nodemailer를 사용해서 이메일 보내는 방법 feat. 문의하기 form 폼, Gmail 설정하기`에서 확인할 수 있습니다.

이번에는 파일 전송방법만 추가로 알아보고, 자세한 내용은 이전 포스팅을 참고하시면 됩니다.

파일을 전송하고 파일의 Base64 문자열로 변환하는 방법은 두가지 동류가 있습니다.

  1. 클라이언트에서 파일을 받아서 서버로 전송하고 서버에서 파일을 Base64 문자열로 변환하여 이메일로 전송하는 방법

  2. 클라이언트에서 파일을 Base64 문자열로 변환하여 서버로 전송하고 서버에서 이메일로 전송하는 방법

Base64 문자열로 변환하는 방법은 클라이언트와 서버에서 모두 가능합니다.

이번 포스팅에서는 1번 방법을 사용하여 파일을 전송하고 Base64 문자열로 변환하는 방법을 알아보겠습니다.

목차

  1. Form Ui 만들기
  2. nodemailer 설치하기
  3. 서버액션 함수 만들기
    1. 서버액션 설정
    2. Form 컴포넌트 수정
    3. 파일을 Base64 문자열로 변환하기
    4. nodemailer attachment 추가하기
    5. 파일 크기 제한하기

1. Form Ui 만들기

이메일 파일전송을 구현하기 위해 먼저 파일전송을 할 수 있는 Form Ui를 만들어야 합니다.

components/Form.js
'use client';
 
const Form = () => {
  return (
    <form action="">
      <div className="flex flex-col gap-4 text-black">
        <div>
          <label htmlFor="name">이름: </label>
          <input type="text" id="name" name="name" placeholder="이름" />
        </div>
        <input type="file" name="file" />
      </div>
      <button type="submit">전송</button>
    </form>
  );
};
 
export default Form;

간단한 Form 구현 방법이므로 이름과 파일을 전송할 수 있는 Form을 만들었습니다.

server Action, useFormState를 사용하기 때문에 반드시 name 속성을 추가해야 합니다.


2. nodemailer 설치하기

nodemailer 패키지를 설치합니다.

npm install nodemailer

만약 타입스크립트를 사용한다면 @types/nodemailer도 설치합니다.

npm install --save-dev @types/nodemailer

3. 서버액션 함수 만들기

3.1. 서버액션 설정

nodemailer를 설치했다면 다음 action에서 함수를 만들어 줍니다.

우선 이메일 로직은 제외하고 파일이 안전하게 잘 들어가는지 확인합니다.

lib/action.ts
'use server';
export const sendEmail = async (prevState: any, formData: FormData) => {
  try {
    const { name, file } = Object.fromEntries(formData);
 
    console.log(name, file);
 
    return { message: '파일 전송 성공' };
  } catch (error) {
    console.error(error);
  }
};

3.2. Form 컴포넌트 수정

이제 Form 컴포넌트에서 파일을 전송할 수 있도록 수정합니다.

components/Form.js
'use client';
 
import { sendEmail } from '@/lib/action';
import { useFormState } from 'react-dom';
 
const Form = () => {
  const [actionState, formAction] = useFormState(sendEmail, null);
 
  return (
    <form action={formAction}>
      <div className="flex flex-col gap-4 text-black">
        <div>
          <label htmlFor="name">이름: </label>
          <input type="text" id="name" name="name" placeholder="이름" />
        </div>
        <input type="file" name="file" />
      </div>
      <button type="submit">전송</button>
    </form>
  );
};
 
export default Form;

간단히 formAction을 추가하여 파일을 전송할 수 있도록 수정했습니다.

이제 이름과 파일을 첨부하여 전송버튼을 클릭하고 콘솔에 이름과 파일이 잘 출력되는지 확인합니다.

저는 ts.png 파일을 첨부하여 전송했습니다.

다음 이미지처럼 터미널에서 파일이 잘 출력되는지 확인합니다.

파일 전송시 터미널 콘솔 화면

파일 전송시 터미널 콘솔 화면

클라이언트에서 파일이 잘 받아 왔습니다.

이제 받은 파일을 Base64 문자열로 변환 변환하는 작업을 해야합니다.

3.3. 파일을 Base64 문자열로 변환하기

lib/action.ts
export const sendEmail = async (prevState: any, formData: FormData) => {
  try {
    const { name, file } = Object.fromEntries(formData);
 
    let dataUrl;
 
    if (file instanceof File) {
      const buffer = Buffer.from(await file.arrayBuffer());
      const base64String = buffer.toString('base64');
 
      dataUrl = `data:${file.type};base64,${base64String}`;
    } else {
      console.error('파일이 파일의 인스턴스가 아닙니다.');
    }
 
    console.log(name, dataUrl);
 
    return { message: '파일 전송 성공' };
  } catch (error) {
    console.error(error);
  }
};

위 코드에서 하이라이트 부분을 추가합니다.

타입스크립트를 사용하고 있기 때문에 조건문을 사용하여 File 인스턴스인지 확인해야합니다.

만약 타입스크립트를 사용하고 있지 않다면 조건문을 사용하지 않아도 됩니다.

Buffer.from 메서드를 사용하여 파일을 읽어온 후 base64 문자열로 변환합니다.

base64 문자열로 변환한 파일을 dataUrl에 저장합니다.

dataUrl은 받은 파일을 Base64 문자열로 변환하고 파일의 MIME 타입을 인코딩 한 문자열입니다.

이제 콘솔을 확인하여 파일이 잘 변환되었는지 확인합니다.

다음과 같이 파일이 변환된 내용이 나타나면 성공입니다.

홍길동 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAb///8rdcVfktA0esd3odYSbcLs8fktdsUgcc
# ... 생략

이름과 문자로 변환된 매우 긴 문자열이 나타납니다. 이런 문자가 나타나면 성공입니다.

이제 nodemailer를 사용하여 파일을 전송하는 방법을 추가해보겠습니다.

이전 포스팅 `Next.js와 nodemailer를 사용해서 이메일 보내는 방법 feat. 문의하기 form 폼, Gmail 설정하기`에서 설명한 것과 거의 비슷합니다.

아래 코드는 .env 설정과 이전 포스팅에서 설명한 내용을 생략한 내용입니다.

3.4. nodemailer attachment 추가하기

파일을 전송할 때는 다음과 같이 추가해야합니다.

lib/action.ts
'use server';
 
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,
  },
});
 
export const sendEmail = async (prevState: any, formData: FormData) => {
  try {
    const { name, file } = Object.fromEntries(formData);
 
    // 간단한 유효성 검사
    if (!name || !file) {
      return { message: '모든 필드를 입력해주세요' };
    }
 
    let dataUrl;
 
    if (file instanceof File) {
      const buffer = Buffer.from(await file.arrayBuffer());
      const base64String = buffer.toString('base64');
 
      dataUrl = `data:${file.type};base64,${base64String}`;
    } else {
      console.error('파일이 파일의 인스턴스가 아닙니다.');
    }
 
    await transporter.sendMail({
      from: process.env.GMAIL_USER,
      to: process.env.GMAIL_USER,
      subject: `보낸이: ${name}`,
      html: `<p>보낸이: ${name}</p>`,
      attachments: [{ path: dataUrl }],
    });
 
    return { message: '이메일 전송 성공' };
  } catch (error) {
    console.error(error);
  }
};

이메일로 파일을 보내기 위해서는 attachments 속성을 사용해야합니다.

attachments 속성에 파일을 전송할 때는 path 속성에 Base64 문자열로 변환된 파일을 넣어주어야합니다.

위 코드에서 하이라이트 부분을 추가합니다.

이제 파일을 전송할 수 있습니다.

form에서 작은 이미지 파일을 첨부하고 전송버튼을 클릭하고 파일을 보내 봅시다.

check! 이상한곳 보내지말고 나의 이메일로 보내세요!

메일이 성공적으로 전송되었다면 성공입니다.! 🎉

3.5. 파일 크기 제한하기

마지막으로 필요한것은, 사용자가 아무 파일이나 용량이 큰 파일을 보내는 것을 방지하기 위해 파일의 크기 및 파일 타입 제한하는 방법을 추가하면 됩니다.

예시 입니다.

lib/action.ts - 예시
const MAX_FILE_SIZE = 1024 * 1024 * 4; // 4MB
 
export const sendEmail = async (prevState: any, formData: FormData) => {
  try {
    const { name, file } = Object.fromEntries(formData);
 
    // 간단한 유효성 검사
    if (!name || !file) {
      return { message: '모든 필드를 입력해주세요' };
    }
 
    let dataUrl;
 
    if (file instanceof File) {
      // 파일 크기 제한
      if (file.size > MAX_FILE_SIZE) {
        return { message: '파일 크기는 4MB 이하로 제한됩니다.' };
      }
 
      if (!file.type.includes('image')) {
        return { message: '이미지 파일만 전송 가능합니다.' };
      }
 
      const buffer = Buffer.from(await file.arrayBuffer());
      const base64String = buffer.toString('base64');
 
      dataUrl = `data:${file.type};base64,${base64String}`;
    } else {
      console.error('파일이 파일의 인스턴스가 아닙니다.');
    }
 
    // ... 동일한 코드 (생략)
  } catch (error) {
    console.error(error);
  }
};

조건문을 사용해서 파일의 크기를 제한할 수 있습니다.

또한 파일의 확장자가 이미지인지 pdf인지 확인하고, 파일의 타입을 제한할 수도 있습니다.

이상으로 서버 액션과, 클라이언트에서 파일을 받아서 서버에서 파일의 Base64 문자열로 변환하여 이메일로 전송하는 방법을 알아보았습니다.


관련 태그