9 Transactional Email Templates You Can Copy-Paste
Transactional email templates are the emails a product sends automatically — a welcome note, a receipt, a reset link.
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Welcome email
- 02 Receipt / order confirmation email
- 03 Password reset email
- 04 Notification email
- 05 Shipping update email
- 06 Email verification code
- 07 Invoice / subscription billing email
- 08 Appointment confirmation email
- 09 Security alert email
The order follows what a single user actually goes through, not popularity: the welcome email right after signup, the receipt from a first purchase, password reset and a verification code when an account gets forgotten, a routine notification, a shipping update after buying something, a monthly invoice, a booking confirmation, and finally a security alert when something looks off. All nine share the same <table role="presentation"> skeleton and a max-width: 400px fluid card, so they drop straight into an ESP's HTML paste box.
01Welcome email
The first email a signup triggers automatically. The badge bounces gently every two seconds, and clicking the button flips its label to "Started ✓" right where it sits.
<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>
02Receipt / order confirmation email
Sent right after checkout completes. Tapping "view receipt" flips grid-template-rows from 0fr to 1fr, actually expanding two order lines.
.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; }
03Password reset email
A real countdown ticks the link's expiry from 10:00 down to zero. setInterval shaves a second off every tick and redraws the mm:ss string.
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);
04Notification email
The generic notification that shows up most often — a comment, a mention. Clicking "view comment" makes the unread dot actually vanish and swaps the label to "read".
cta.addEventListener('click', function (e) {
e.preventDefault();
ema.classList.remove('is-demo');
ema.classList.add('is-read');
cta.textContent = '읽음';
});
05Shipping update email
The progress bar auto-fills on a two-second loop, and clicking "track shipment" expands the real three-step delivery timeline. It fills with transform: scaleX(), not width, so it never forces a layout recalc.
.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); }
}
06Email verification code
The code's letter-spacing breathes in and out on a loop, and the copy button really puts it on the clipboard. When navigator.clipboard is missing (older webviews), it falls back to a hidden <textarea> plus document.execCommand('copy') — and shows an honest "copy failed" if even that fails.
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 ? '복사됨' : '복사 실패 — 직접 선택하세요';
}
07Invoice / subscription billing email
The billed amount actually counts up from 0 to 12,900 won via requestAnimationFrame, and clicking through expands a line-item breakdown.
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);
08Appointment confirmation email
The date badge flips over with rotateY every two seconds, and "add to calendar" really swaps its label to "Added ✓" on click.
.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); }
}
09Security alert email
The new-login badge glows in the $error token color on a loop, and clicking through flips the button to "Protected ✓" — welcome to security alerts, and the only one of the nine that trades brand blue for a warning color, because reassurance and a threat shouldn't look the same.
.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); }
}
Where it breaks — the trap
The biggest trap is that Outlook's desktop rendering engine (Word, not a browser) supports no @keyframes, no flexbox, and little modern CSS. That's why every one of the nine is laid out with <table role="presentation">, with width="100%" and style="max-width:400px" together so the card still holds its shape in clients that render fixed table widths. The same reasoning is behind the <!--[if mso]> / <![endif]--> conditional comment pair wrapping each card in its own Outlook-only table. The second trap actually bit us in the code — writing font: 700 12px/1 inherit; makes inherit an invalid value in the font-family slot, which silently kills the whole declaration and falls back to the browser default (around 13px). Moving font-family onto its own line and leaving font: 700 12px/1; behind does not revive it either, because a shorthand with no family is still invalid — the fix is to drop the shorthand and write font-weight: 700; font-size: 12px; line-height: 1; as separate properties, letting the family inherit, or else to name a real font family inside the shorthand. The third was size: all nine first overflowed the 480×300 stage enough to trigger a scrollbar, which cutting cell padding from 8px to 4px and pulling the dark-mode button out of the card's layout with position: absolute fixed — but the six with an extra row (a toggle button, a countdown, a progress track) still overflowed the 320×200 phone check by 10 to 46px until that row's own inner padding got the same treatment. The fourth was item 02's shimmer — it originally moved for only 0.7 of the 2-second loop, registering just 4 of 23 measured frames as "moving" (the floor is 6); stretching the moving window to 1.8 seconds brought it up to 16. The vanilla version inside the zip, opened with 4m43f23g, reflects all four fixes.
Accessibility
All nine turn off the badge and progress-bar loop animations under prefers-reduced-motion: reduce (flip, pulse, glow, and the fill all get animation: none !important), while the actual state changes a click produces — marked read, expanded, done — stay intact. Every button is a real <button> or role="button" link, so keyboard focus plus Enter/Space work, and expandable rows carry aria-expanded while toggle buttons carry aria-pressed. The verification code's copy feedback (06) sits in aria-live="polite", so a screen reader announces "copied" without moving focus. Dark mode ships both ways: a manual toggle button and <meta name="color-scheme" content="light dark"> plus @media (prefers-color-scheme: dark), so clients that honor system dark mode get it without the button.
More email templates live in the template category, and what this site covers overall is on the about page. Client-by-client CSS support is worth checking at Can I email before any of these ship for real.
FAQ
Can I actually paste these into an email service and send them?
Yes — that's what they're built for. The vanilla version in the zip is table-based layout plus near-inline styles plus a max-width:400px fluid card, so it drops straight into the HTML paste box in Mailchimp, SendGrid, or Resend. 400px is a narrower width chosen for a compact transactional card rather than a newsletter — change the max-width value if you need something wider. Always re-check Can I email against whatever client your recipients actually use before sending.
Why are eight of the nine blue and only 09 red?
Eight share the brand blue ($subject-blue) on the CTA and badge, so they read as one company's mail. Only the security alert (09) uses the error token ($error) instead — an email meant to make someone pause shouldn't use the same color as one meant to reassure them.
Is the dark-mode preview button the same as real dark mode support?
Yes, same rules. The button toggles an .is-dark class that forces the exact background and text-color rules the @media (prefers-color-scheme: dark) block already applies automatically — so what the button shows is what a client like Apple Mail or Outlook.com renders on its own when the system is in dark mode.