이메일 템플릿 디자인 9종 — 웰컴부터 보안 알림까지
이메일 템플릿 디자인은 완성된 뉴스레터 하나를 예쁘게 꾸미는 일이 아니라, 웰컴·영수증·비밀번호 재설정처럼 서비스가 실제로 자동 발송하는 아홉 가지 "거래형" 메일 종류별로 나눠 만드는 일입니다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 웰컴 이메일
- 02 영수증 · 주문 확인 이메일
- 03 비밀번호 재설정 이메일
- 04 알림 이메일
- 05 배송 안내 이메일
- 06 이메일 인증 코드 메일
- 07 청구서 · 구독 결제 이메일
- 08 예약 확인 이메일
- 09 보안 알림 이메일
아홉 개는 인기순이 아니라 한 사용자가 서비스를 겪는 순서대로 놓았습니다. 가입 직후 받는 웰컴 메일, 첫 결제의 영수증, 계정을 잊었을 때의 비밀번호 재설정과 인증 코드, 평소에 오는 알림, 물건을 샀을 때의 배송 안내, 매달 오는 청구서, 예약을 잡았을 때의 확인 메일, 마지막으로 보안이 걱정될 때의 로그인 경고 순입니다. 아홉 개 전부 표 기반 레이아웃(<table role="presentation">)과 max-width: 400px 유동형 카드로 짜여 있어, 실제 이메일 서비스(ESP)에 그대로 붙여 보낼 수 있습니다.
01웰컴 이메일
가입 직후 자동으로 나가는 첫 이메일입니다. 배지가 2초마다 살짝 튀어 오르며 첫인상을 만들고, 버튼을 누르면 자리에서 바로 "시작했어요 ✓"로 라벨이 바뀝니다.
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" class="ema__card" style="max-width:400px">
<tr><td class="ema__brand">◆ Acme</td></tr>
<tr><td align="center" class="ema__hero"><span class="ema__badge">👋</span></td></tr>
<tr><td align="center" class="ema__title">환영합니다, 지훈님!</td></tr>
<tr><td align="center" class="ema__ctawrap">
<a href="#" role="button" class="ema__cta" id="cta">시작하기</a>
</td></tr>
</table>
02영수증 · 주문 확인 이메일
결제 직후 자동 발송되는 주문 확인 메일입니다. "영수증 보기"를 누르면 grid-template-rows가 0fr에서 1fr로 바뀌면서 주문 내역 2건이 실제로 펼쳐집니다.
.ema__detail {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows $dur-base $ease-spring;
}
.ema.is-open .ema__detail { grid-template-rows: 1fr; }
.ema__detailin { overflow: hidden; }
03비밀번호 재설정 이메일
링크 만료까지 남은 시간이 10분에서 0으로 실제로 줄어드는 카운트다운이 들어 있습니다. 1초마다 setInterval로 남은 초를 깎아 mm:ss 형식으로 다시 그립니다.
var left = 600;
setInterval(function () {
if (left <= 0) return;
left -= 1;
var m = Math.floor(left / 60), s = left % 60;
cd.textContent = (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
}, 1000);
04알림 이메일
댓글·언급처럼 가장 자주 오는 범용 알림 메일입니다. "댓글 보기"를 누르면 안읽음 점이 실제로 사라지고 버튼 라벨이 "읽음"으로 바뀝니다.
cta.addEventListener('click', function (e) {
e.preventDefault();
ema.classList.remove('is-demo');
ema.classList.add('is-read');
cta.textContent = '읽음';
});
05배송 안내 이메일
진행바가 2초 주기로 자동으로 채워지고, "배송 조회"를 누르면 배송 3단계(준비·발송·완료)가 실제로 펼쳐집니다. 진행바는 width가 아니라 transform: scaleX()로 채워 레이아웃을 다시 계산하지 않습니다.
.ema__fill { transform-origin: left; }
.ema.is-demo .ema__fill { animation: ema-progress 2s ease-in-out infinite; }
@keyframes ema-progress {
0%, 15% { transform: scaleX(.18); }
55%, 70% { transform: scaleX(.86); }
100% { transform: scaleX(.18); }
}
06이메일 인증 코드 메일
인증 코드 글자 간격이 숨쉬듯 벌어졌다 좁아지고, "복사" 버튼은 실제로 클립보드에 코드를 담습니다. navigator.clipboard가 없는 환경(오래된 웹뷰 등)에서는 숨김 <textarea>에 코드를 넣고 선택한 뒤 document.execCommand('copy')로 진짜 복사를 시도하고, 그마저 실패하면 "복사됨" 대신 "복사 실패"를 정직하게 보여 줍니다.
function copyCode(code) {
var ok = false;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(code); ok = true;
} else {
var ta = document.createElement('textarea');
ta.value = code; ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta); ta.select();
try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
document.body.removeChild(ta);
}
copystate.textContent = ok ? '복사됨' : '복사 실패 — 직접 선택하세요';
}
07청구서 · 구독 결제 이메일
0원에서 시작한 청구 금액이 requestAnimationFrame으로 실제로 12,900원까지 올라갑니다. 버튼을 누르면 청구 내역이 펼쳐집니다.
var target = 12900, t0 = null;
function step(ts) {
if (!t0) t0 = ts;
var p = Math.min(1, (ts - t0) / 1200);
amt.textContent = Math.round(target * p).toLocaleString('ko-KR') + '원';
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
08예약 확인 이메일
날짜 배지가 2초마다 rotateY로 뒤집히고, "캘린더에 추가"를 누르면 실제로 라벨이 "추가됨 ✓"으로 바뀝니다.
.ema__badge { transform-style: preserve-3d; }
.ema.is-demo .ema__badge { animation: ema-flip 2s ease-in-out infinite; }
@keyframes ema-flip {
0%, 20% { transform: rotateY(0); }
50%, 65% { transform: rotateY(180deg); }
100% { transform: rotateY(360deg); }
}
09보안 알림 이메일
새 로그인 배지가 경고색($error 토큰)으로 은은하게 번지고, 버튼을 누르면 실제로 "보호 완료됨 ✓"으로 바뀝니다. 색은 다른 여덟 개의 브랜드 블루가 아니라 오류 색을 그대로 씁니다 — 안심시키는 알림이 아니라 경계해야 하는 알림이기 때문입니다.
.ema.is-demo .ema__badge { animation: ema-glow 2s ease-in-out infinite; }
@keyframes ema-glow {
0%, 100% { box-shadow: 0 0 0 0 rgba(194, 54, 26, .4); }
50% { box-shadow: 0 0 0 12px rgba(194, 54, 26, 0); }
}
어디서 깨지는가 — 함정
가장 크게 걸린 함정은 아웃룩(데스크톱)이 @keyframes·flexbox·많은 최신 CSS를 지원하지 않는다는 것입니다. 그래서 아홉 개 전부 레이아웃은 <table role="presentation">로 짜고, 폭은 width="100%"에 style="max-width:400px"를 같이 줘 표 폭이 고정되는 클라이언트에서도 무너지지 않게 했습니다. <!--[if mso]>~<![endif]--> 조건부 주석으로 아웃룩만 별도 표 래퍼를 씌운 것도 같은 이유입니다. 두 번째 함정은 실제로 코드에서 겪었습니다 — 제목·버튼 글자에 font: 700 12px/1 inherit;처럼 축약형을 쓰면 inherit이 font-family 자리에서 무효값이 되어 선언 전체가 죽고, 브라우저 기본 글꼴(13px 근처)로 조용히 되돌아갑니다. font-family만 다음 줄로 빼고 font: 700 12px/1;을 그대로 두어도 글꼴 이름이 빠진 단축은 여전히 무효라 살아나지 않으니, 단축을 버리고 font-weight: 700; font-size: 12px; line-height: 1;처럼 낱개 속성으로 쓰고 글꼴은 상속에 맡기거나(또는 단축 안에 실제 글꼴 이름까지 적어야) 시스템 서체가 제대로 걸립니다. 세 번째는 크기입니다 — 처음 아홉 개 모두 480×300 무대에서 스크롤바가 뜰 만큼 넘쳤고, 셀 안쪽 여백을 8px에서 4px로 줄이고 다크 모드 버튼을 카드 레이아웃 밖(절대 위치)으로 빼 480×300은 잡았지만, 320×200 폰 화면에서는 펼침 버튼·카운트다운 같은 "추가 줄"이 있는 여섯 개가 여전히 10~46px 넘쳤습니다. 그 여섯 개의 추가 줄 안쪽 요소(버튼·목록 줄·진행바 여백)까지 별도로 줄여야 320×200도 잡혔습니다. 네 번째는 02번 영수증의 반짝임 애니메이션이었습니다 — 처음엔 2초 중 0.7초만 움직여 23프레임 중 4프레임만 "움직인 프레임"으로 잡혔고(하한 6), 움직이는 구간을 2초 중 1.8초로 늘리고서야 16프레임으로 올랐습니다. 압축 비밀번호 4m43f23g로 열리는 zip 안 vanilla 버전은 이 네 가지를 전부 반영한 최신본입니다.
접근성
아홉 개 전부 prefers-reduced-motion: reduce에서 배지·진행바의 반복 애니메이션을 끄되(뒤집힘·펄스·글로우·진행바 채움 전부 animation: none !important), 클릭으로 바뀌는 실제 상태(읽음 처리·펼침·완료 표시)는 그대로 남깁니다. 버튼은 전부 role="button" 또는 실제 <button> 요소로 키보드 포커스와 Enter·Space 조작이 되고, 펼침 요소는 aria-expanded로, 토글형 버튼은 aria-pressed로 지금 상태를 알립니다. 인증 코드 복사(06)의 상태 문구는 aria-live="polite"라 스크린 리더가 화면을 옮기지 않아도 "복사됨"을 읽어 줍니다. 다크 모드는 수동 토글 버튼과 함께 <meta name="color-scheme" content="light dark"> + @media (prefers-color-scheme: dark)도 같이 넣어, 시스템 다크 모드를 지원하는 클라이언트에서는 버튼 없이도 자동으로 맞춰집니다.
다른 이메일 소재는 템플릿 카테고리에 있고, 이 사이트가 무엇을 다루는지는 소개에 있습니다. 이메일 클라이언트별 CSS 지원 범위는 Can I email에서 항목별로 확인할 수 있습니다.
FAQ
이 템플릿을 실제 이메일 서비스(ESP)에 그대로 써도 되나요?
네, 그러려고 만들었습니다. zip 안 vanilla 버전은 표 기반 레이아웃 + 인라인에 가까운 스타일 + max-width:400px 유동형 카드로 짜여 있어 Mailchimp·SendGrid·Resend 같은 서비스의 HTML 붙여넣기 칸에 그대로 넣을 수 있습니다. 400px 은 뉴스레터보다 좁은 거래형 카드에 맞춘 폭이라, 필요하면 max-width 값만 바꿔 더 넓은 레이아웃에 맞출 수 있습니다. 다만 실제 발송 전엔 항상 Can I email로 그 서비스가 쓰는 클라이언트의 지원 범위를 한 번 더 확인하십시오.
왜 아홉 개 색이 다 파란색이고 09번만 빨간색인가요?
여덟 개는 브랜드 블루($subject-blue)를 CTA·배지 색으로 공유해 한 회사가 보내는 메일처럼 통일감을 주었고, 09번 보안 알림만 오류 토큰($error)을 썼습니다. 안심시켜야 하는 메일과 경계해야 하는 메일은 색부터 달라야 한다고 판단했습니다.
다크 모드 미리보기 버튼은 실제 다크 모드와 같은 동작인가요?
네, 같은 CSS 규칙을 씁니다. 버튼은 .is-dark 클래스를 토글해 @media (prefers-color-scheme: dark)와 똑같은 배경·글자색 규칙을 강제로 켜는 것이라, 버튼을 눌러 보는 화면이 실제 다크 모드 지원 클라이언트(Apple Mail·Outlook.com 등)에서 보이는 화면과 같습니다.