react-email과 nodemailer로 있어보이는 이메일 시스템 구축하기

avatar
kidow
@kidow
react-email과 nodemailer로 있어보이는 이메일 시스템 구축하기
태그
Nodemailer
React
Email
설명
이제 개발자도 직접 이메일을 이쁘게 디자인할 수 있습니다.
배포
배포
수정일
Nov 9, 2023 12:16 PM
생성일
Nov 6, 2023 07:27 AM
오늘은 react-email이라는 라이브러리를 통해 이메일 템플릿을 직접 디자인하고, nodemailer를 통해 실제로 보내는 것까지 구현해보려고 해요.
제가 운영하고 있는 일간 ProductHunt는 지금껏 슬랙, 디스코드 등 서드파티를 이용해 콘텐츠를 전달하곤 했는데요, 최근 소개했던 Resend라는 프로덕트가 React로 이메일 템플릿을 디자인할 수 있는 오픈 소스를 따로 관리하고 있길래 한 번 이메일로도 뉴스레터를 보낼 수 있는지 테스트해보려고 합니다. 이렇게 좋은 오픈 소스를 기업 입장에서 관리하는 것도 마케팅적으로 좋은 것 같습니다.
 

흥미로운 기능

react-email은 React Component로 이메일 UI를 디자인할 수 있도록 해줍니다. 예를 들면 다음과 같이 내장된 컴포넌트를 사용하여 JSX 코드를 짤 수 있습니다.
import { Button } from "@react-email/button"; import { Html } from "@react-email/html"; import * as React from "react"; export default function Email() { return ( <Html> <Button href="https://example.com" style={{ background: "#000", color: "#fff", padding: "12px 20px" }} > Click me </Button> </Html> ); }
Button, Image, Link 등 다양한 내장 컴포넌트들을 제공합니다. tailwind도 지원한다는 점이 매력적입니다.
Tailwind 컴포넌트
Tailwind 컴포넌트
 
Resend를 비롯한 다른 이메일 소프트웨어와의 통합도 제공합니다.
Nodemailer로 이메일 보내기
Nodemailer로 이메일 보내기
 
다양한 템플릿을 디자인할 수 있는 개발 서버
다양한 템플릿을 디자인할 수 있는 개발 서버
또한 디자인을 위한 개발 서버를 CLI로 따로 열 수가 있습니다. 덕분에 이 곳에서 디자인을 하고 내보낼 때 임포트만 하면 됩니다.
notion image
마지막으로 실시간으로 편집하면서 실제 이메일로 테스트를 할 수도 있습니다.

react-email 설치

현재 개발 중인 프로젝트에 통합하기에는 렌더링 관련해서 문제가 있는 것 같더라구요. 따라서 프로젝트 안에 서브 프로젝트로 분리합니다.
mkdir templates cd templates npm init -y npm install react-email @react-email/components -E
package.json에서 명령어를 추가해줍니다.
{ "name": "email", "version": "1.0.0", "scripts": { "dev": "email dev" }, "dependencies": { "@react-email/components": "^0.0.11", "react-email": "1.9.5" } }
npm run dev 를 실행하면 기본적으로 react-email은 emails 폴더 안에 있는 jsx 파일들을 가리키게 됩니다. 폴더와 파일을 생성합니다.
// templates/emails/newsletter.tsx import { Body, Head, Html, Preview, Tailwind, Text } from '@react-email/components' import * as React from 'react' interface Props {} export const Newsletter = ({}: Props) => { return ( <Html> <Head /> <Preview>Preview</Preview> <Tailwind> <Body className="bg-black text-white my-auto mx-auto font-sans"> <Text>Hello Email!</Text> </Body> </Tailwind> </Html> ) } export default Newsletter
이 컴포넌트를 html 문자열로 변환해주는 함수는 render 입니다. 클라이언트에서 이 tsx코드를 변환해주는 컴포넌트를 따로 만듭니다. 별개의 개발 서버를 켜야 합니다.
// app/(product)/email.tsx 'use client' import { render } from '@react-email/render' import Newsletter from 'templates/emails/newsletter' export default function Email() { const onClick = async () => { const html = render(<Newsletter />) console.log('html', html) } return <button onClick={onClick}>email test</button> }
콘솔로 찍어보면 다음과 같이 나옵니다.
notion image
이제 nodemailer를 설치합니다.
npm install nodemailer npm install @types/nodemailer -D
nodemailer는 nodejs 환경에서만 동작하기 때문에 api 경로를 통해야 합니다. 하나의 임시 경로를 만들어 봅시다.
// app/api/test/route.ts import { NextResponse } from 'next/server' import nodemailer from 'nodemailer' export async function POST(req: Request) { const body = await req.json() const transporter = nodemailer.createTransport({ service: 'gmail', auth: { user: '[보내는 사람 이메일]', pass: process.env.APP_PASSWORD } }) const result = await transporter.sendMail({ from: '[보내는 사람 이메일]', to: '[보낼 사람 이메일]', subject: 'hello world', html: body.html }) return NextResponse.json(result) }
클라이언트 쪽을 다시 손봐줍니다.
// app/(product)/email.tsx 'use client' import { render } from '@react-email/render' import Newsletter from 'templates/emails/newsletter' export default function Email() { const onClick = async () => { const html = render(<Newsletter />) await fetch('/api/test', { method: 'POST', headers: new Headers({ 'Content-Type': 'application/json' }), body: JSON.stringify({ html }) }) } return <button onClick={onClick}>email test</button> }
한 번 보내볼까요?
잘 온 것 같습니다.
잘 온 것 같습니다.
그럼 이제… 이쁘게 꾸며 볼 일만 남았습니다. 기존에 웹에서 보여주던 UI과 유사하게 보내보는 걸로 해봅시다.
뚝딱뚝딱
뚝딱뚝딱
 
완성되었습니다… 미리보기를 한 번 볼까요?
notion image
어떤가요? 아주 간단하게 만들어 보았답니다.
코드는 다음과 같습니다.
import { Body, Column, Container, Font, Head, Html, Img, Link, Preview, Row, Section, Tailwind, Text } from '@react-email/components' import * as React from 'react' interface Props { title: string name: string intro: string url: string } export const Newsletter = ({ title = '타이틀', name = '일간 ProductHunt', intro = '매일매일 선별된 ProductHunt 콘텐츠를 가져다 주는 웹서비스', url = 'https://daily-producthunt.kidow.me' }: Props) => { return ( <Html lang="ko"> <Head> <Font fontFamily="Pretendard-Regular" fallbackFontFamily="sans-serif" fontWeight={400} webFont={{ url: 'https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff', format: 'woff' }} fontStyle="normal" /> </Head> <Preview>{title}</Preview> <Tailwind config={{ theme: { extend: { colors: { brand: '#da552f' } } } }} > <Body className="bg-white"> <Container> <Section className="h-20"> <Row> <Column> <Img src="https://daily-producthunt.kidow.me/logo-circle.png" className="h-10 w-10" /> </Column> </Row> </Section> <Section className="border border-neutral-200 border-solid rounded overflow-hidden"> <Section> <Img className="w-full" src="https://daily-producthunt.kidow.me/opengraph-image.png?c7b23718c68e315c" /> </Section> <Section className="pt-5 px-10"> <Text className="text-2xl font-bold">{name}</Text> <Text className="text-lg">{intro}</Text> </Section> <Section className="text-center pb-5"> <Text> <Link href={url} className="py-2.5 px-5 text-white bg-brand cursor-pointer font-semibold rounded" > 보러 가기 </Link> </Text> </Section> </Section> </Container> </Body> </Tailwind> </Html> ) } export default Newsletter
이메일로 보내면 이렇게 옵니다.
notion image
 
notion image
오늘의 내용은 여기까지입니다. 일단 실제로 프로젝트에 도입할 지는 좀 더 테스트해봐야 할 것 같습니다. 봐주셔서 감사합니다. 👍
 

참고

  • Nodemailer
  • React
  • Email