UI/UX 개발자로 실무를 진행 하면서 수많은 최신 웹 기술의 발전을 체감해 왔습니다. React, Vue.js 같은 프레임워크와 Flexbox, Grid 같은 모던 CSS 레이아웃 덕분에 화면을 구성하는 작업은 과거에 비해 비약적으로 편리해졌습니다. 하지만 단 한 곳, ‘이메일 템플릿’ 영역만큼은 예외였습니다.
이번 반장노트 프로젝트에서 사용자에게 발송되는 3종의 이메일 (아이디 찾기, 비밀번호 재설정, 인증번호 안내) 마크업 및 컴포넌트화 업무를 담당하게 되었습니다.
평소처럼 편리하게 UI를 구성하면 될 것이라 예상했지만, 이메일 템플릿의 세계는 일반적인 최신 웹 프론트엔드 생태계와는 전혀 다른, 마치 과거로 회귀한 듯한 규칙이 지배하고 있습니다.
본 글에서는 이메일 렌더링 환경의 특수성과 한계를 분석하고, 이를 극복하기 위해 React Email 라이브러리를 도입하여 모노레포 환경에서 마주친 트러블슈팅, 그리고 백엔드 팀과 원활하게 협업하기까지의 실무 중심 경험을 공유하고자 합니다.
이메일 렌더링의 딜레마 : 왜 우리는 여전히 <table>을 써야하는가?
웹 브라우저(Chrome, Safari 등)은 ‘웹 표준’이라는 공통된 규칙을 따르지만, 이메일 클라이언트들은 각자 자기들만의 방식으로 HTML을 읽어 들입니다. 이메일 템플릿을 일반 웹 페이지처럼 <div>로 개발할 수 없는 구체적인 이유는 다음과 같습니다.
첫째. 마이크로 소프트 아웃룩(Outlook)의 렌더링 엔진의 한계
기업 환경에서 가장 널리 쓰이는 아웃룩은 2007 버전부터 웹 브라우저 엔진이 아닌 MS Word의 문서 렌더링 엔진을 사용하기 시작했습니다. 워드는 문서 편집기이지 웹 브라우저가 아니기 때문에 margin, padding, float, flex 같은 최신 css 레이아웃을 제대로 이해하지 못하고 화면을 완전히 깨뜨려 버립니다.
아웃룩에서 레이어 붕괴를 막는 유일한 방법은 과거 방식인 <table>구조로 뼈대를 잡는 것뿐입니다.
둘째. 웹메일 서비스의 보안 및 스타일 필터링 정책
지메일(Gmail)이나 네이버 메일 등은 사용자가 웹 브라우저 위에 메일을 확인합니다.
만약 이메일 본문에 <style> body{background : black; }<style> 같은 코드가 포함되어 있다면, 자칫 네이버 메일 웹사이트 전체의 배경이 까맣게 변해버릴 위험이 있습니다. 이러한 스타일 충돌과 보안이슈를 막기 위해 주요 웹메일 서버들은 코드 내의 <style> 태그나 외부 CSS<link>를 임의로 삭제해 버립니다.
결국, 최신 스마트폰 Apple 메일부터 구형 아웃룩 까지 모든 환경에서 디자인이 동일하게 보이도록 보장하려면, <table width=”100%”> 태그로 전체 구역을 나누고 모든 스타일을 <td style=”...”> 형태로 주입하는 인라인 스타일(Inline-style)방식을 택해야만 했습니다.
구원투수 ‘React Email’ 도입 및 환경 세팅 트러블슈팅
순수 HTML과 <table>태그를 하드코딩하여 인라인 스타일을 한 땀 한 땀 작성하는 것은 개발 및 유지보수 측면에서 최악의 경험을 초래합니다. 텍스트 하나, 여백 하나를 수정할 때마다 겹겹이 쌓인 <tr>,<td> 태그를 추적해야 했기 떄문입니다. 이를 해결하기 위해 기존 React 생태계를 유지하면서 이메일용 HTML 을 안전하게 추출할 수 있는 React Email 라이브러리를 도입하기로 결정했습니다.
현재 프로젝트는 pnpm 기반의 모노레포(Monorepo) 워크스페이스 환경으로 구성되어 있습니다. 도입 초기, 로컬 미리보기 서버를 띄우기 위해 명령어를 실행했을 때 뜻밖의 에러를 마주했습니다.
> pnpm -F @vizendjs/episode-banjang email sh: react-email: command not found ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL
-
단순히 루트 경로에서 설치하면 될 줄 알았으나, 모노레포 특성상 특정 패키지(@vizendjs/episode-banjang) 내부에서 스크립트를 실행하기 때문에 실행 링크가 깨진 것이 원인이었습니다. 이를 해결하기 위해 해당 워크스페이스를 명시하여 라이브러리를 직접 설치(pnpm -F @vizendjs/episode-banjang add -D react-email)하고, 루트 경로에서 pnpm install을 재실행하여 의존성 링크를 복구함으로써 로컬 대시보드 환경을 성공적으로 구축할 수 있었습니다.
본격적인 템플릿 개발 및 핵심 트러블슈팅
로컬 대시보드(localhost:3000)를 띄워두고 실시간으로 컴포넌트를 렌더링하며 개발을 진행했습니다. 이 과정에서 마주친 두 가지 큰 기술적 난관과 해결책은 다음과 같습니다.
1. 미디어 쿼리 없는 모바일 반응형 대응 (Fluid Table)
일반적인 웹 퍼블리싱에서는 @media 쿼리를 사용해 모바일 뷰를 분기하지만, 앞서 언급했듯 이메일 환경에서는 미디어 쿼리가 무시되는 경우가 많습니다. 따라서 모바일용 코드를 따로 분기하는 대신, 최대 너비(max-width)와 100% 너비를 조합하여 화면이 좁아지면 알아서 줄어들게 만드는 '유연한 테이블(Fluid Table)' 방식을 활용했습니다.
React Email에서 제공하는 <Container> 컴포넌트에 maxWidth: '600px', width: '100%', margin: '0 auto' 스타일을 부여하여, PC에서는 가독성을 위해 600px로 폭을 고정하고, 600px 이하의 모바일 기기에서는 화면에 100% 꽉 차도록 유연하게 대응했습니다.
2. 이미지 렌더링 이슈: Base64의 치명적인 함정
이메일에 포함되는 '반장노트' 로고와 일러스트 이미지의 경로 처리는 가장 깊이 고민했던 지점이었습니다.
처음에는 로컬 테스트 경로(http://localhost:3000/images/...)로 작업했지만, 실제 메일 발송 시 사용자는 해당 로컬 환경에 접근할 수 없어 이미지가 엑박(깨짐)으로 나타납니다. 서버 세팅을 기다리지 않고 자체적으로 해결해 보고자, 이미지를 엄청나게 긴 문자열로 변환하는 Base64 방식을 적용해 HTML 내부에 직접 박아 넣는 방법을 검토했습니다. 하지만 리서치 결과, 이 방식은 이메일 환경에서 두 가지 치명적인 결함이 있었습니다.
메일 내용 잘림 (용량 초과)
지메일(Gmail)의 경우 HTML 코드 용량이 102KB를 넘어가면 메일 하단을 강제로 잘라버리고 "메시지 전체 보기" 링크를 띄웁니다. Base64를 쓰면 코드가 기하급수적으로 길어져 메일 템플릿 전체가 망가지는 현상이 100% 발생합니다.
보안 필터링 (엑박)
아웃룩 등 주요 웹메일에서는 스팸 및 악성코드 방지를 위해 Base64로 삽입된 이미지를 원천 차단해버립니다.
결론적으로 이메일 템플릿의 이미지는 반드시 외부 서버(CDN, S3 등)에 업로드하고 퍼블릭 절대 경로(URL)를 사용해야만 했습니다. 당시 백엔드 서버의 이미지 업로드 경로가 아직 확정되지 않은 상태였기 때문에, 우선 코드상에는 로컬 경로로 연결해 두고 레이아웃을 고정한 후 다음 단계인 백엔드 협업으로 넘어갔습니다.
가독성을 높이는 객체 형태의 인라인 스타일(Inline Style) 관리
이메일 템플릿의 가장 큰 고통 중 하나는 모든 CSS를 태그 안에 직접 적어 넣어야 하는 '인라인 스타일' 방식입니다. 기존 순수 HTML 환경에서는 <td style="font-size: 16px; color: #000; line-height: 1.6; ...">처럼 태그 하나에 스타일이 길게 늘어져 코드의 가독성이 크게 떨어졌습니다.
이번 작업에서는 React 환경의 이점을 살려 이 문제를 자바스크립트 객체(Object)를 활용해 해결했습니다. 복잡한 스타일 속성들을 마크업 내부에 직접 적지 않고, 파일 하단에 독립적인 상수 객체로 분리하여 관리한 것입니다.
// 1. 마크업 영역은 깔끔하게 구조만 파악할 수 있도록 유지
<Text style={codeText}> {verificationCode} </Text>
<Text style={footerText}> 이 코드는 30분 후 만료됩니다.<br />감사합니다. </Text>
// 2. 파일 하단에 스타일 객체를 따로 분리하여 관리
const codeText = {
fontSize: '40px',
fontWeight: 'bold',
color: '#5C5CFF',
letterSpacing: '2px',
marginBottom: '40px',
};
const footerText = {
fontSize: '16px',
color: '#000000',
lineHeight: '1.6',
};
-
이렇게 마크업 영역과 스타일 정의 영역을 물리적으로 분리함으로써, 코드를 한눈에 읽고 구조를 파악하기가 훨씬 수월해졌습니다. 추후 폰트 크기나 색상을 수정해야 할 때도 복잡한 태그 숲을 헤맬 필요 없이 하단의 스타일 객체만 찾아 값을 변경하면 되므로, 기존 HTML 하드코딩 방식과 비교할 수 없을 만큼 개발자 경험(DX)과 유지보수성이 향상되었습니다.
산출물 추출 및 백엔드(서버) 연동을 위한 협업 프로세스
프론트엔드 파트에서 작성한 React 컴포넌트(.tsx)은 그 자체로 메일 서버에서 구동될 수 없으므로, 이를 순수 HTML로 추출(export)하여 백엔드 개발자에게 전달해야 했습니다.
package.json에 설정한 email:export 스크립트를 통해 변환된 .html 파일들을 뽑아낸 뒤, 백엔드 개발자와 원활하게 소통하기 위해 노션(Notion)에 '이메일 템플릿 마크업 산출물 및 연동 가이드'를 상세히 작성했습니다. 단순히 파일만 던져주는 것이 아니라, 서버 연동 시 발생할 수 있는 시행착오를 미리 차단하기 위함이었습니다.
협업을 위한 핵심 가이드 내용
동적 데이터(변수) 치환 포맷 사전 정의: 백엔드 템플릿 엔진에서 회원 정보를 쉽게 바인딩할 수 있도록, HTML 내부에 임시 변수 포맷을 규칙적으로 삽입했습니다. (예: 사용자 이름은 {{userName}}, 6자리 인증번호는 {{verificationCode}} 등)
미확정 이미지 경로에 대한 대처 및 안내: 사내 메신저와 가이드 문서를 통해 "이미지 제작은 완료되었으나 절대 경로가 아직 세팅 전이므로 레이아웃 유지를 위해 영역만 임시 세팅해 두었습니다. 추후 서버 세팅이 완료되면 src 속성을 실서버 URL로 교체 부탁드립니다."라고 명확히 안내했습니다.
이러한 선제적인 공유 덕분에, 백엔드 담당자도 당황하지 않고 *"나중에 이미지 URL만 교체하면 되겠다"*며 즉각적으로 본인들의 연동 작업(SMTP 발송 테스트 등)을 지연 없이 진행할 수 있었습니다.
기술적 제약을 넘어선 문서화와 협업의 가치
이번 반장노트 이메일 템플릿 작업은 구시대적인 웹 표준의 한계와 파편화된 클라이언트 환경을 동시에 고려해야 하는 매우 까다로운 과제였습니다. 하지만 8년 동안 익숙했던 수동 하드코딩 방식에서 벗어나 React Email이라는 모던 기술 스택을 과감히 도입함으로써, 유지보수의 고통에서 벗어나 프론트엔드 개발 환경의 이점을 실무에 성공적으로 안착시킬 수 있었습니다.
가장 의미 있었던 점은 단순히 코드를 짜고 화면을 예쁘게 그리는 데 그치지 않았다는 것입니다. Base64 지양, Fluid Table 구성 등 기술적 대안을 논리적으로 도출하고, 미완성된 인프라(이미지 경로 부재) 상황에서도 백엔드 담당자가 막힘없이 업무를 이어갈 수 있도록 배려하며 가이드 문서를 작성했습니다.
이번 경험을 통해, 개발자에게 있어 '기술력'이란 단순히 최신 라이브러리를 다루는 능력이 아니라, 주어진 제약 상황을 정확히 파악하고 타 부서와의 매끄러운 협업을 이끌어내는 소통과 문서화 능력이라는 점을 다시 한번 깊이 깨달았습니다. 앞으로도 익숙한 방식에 안주하지 않고, 조직 전체의 생산성과 코드 품질을 높일 수 있는 방법을 주도적으로 고민하고 적용해 나가는 퍼블리셔로 성장하겠습니다.
sangsooni