メルマガ デザイン 9種 — Outlookでも崩れない
メルマガ デザインとは、売るために送るメール — ニュースレター、クーポン、タイムセール、リマインド — の見た目を組み立てる仕事です。この9つはテーブル(table)の縦罫を骨組みにしたHTMLとCSSだけで作ってあり、Outlookでも崩れない貼り付け方そのままで動きます。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
- 01 ニュースレター ダイジェスト
- 02 割引クーポンメール
- 03 タイムセール残り時間バー
- 04 カート放棄リマインド
- 05 イベント招待・出欠
- 06 オンボーディング チェックリスト
- 07 星評価アンケートメール
- 08 休眠顧客の呼び戻しメール
- 09 友達紹介コードメール
並び順は流行ではなく、一人の読者がメールを受け取る順です。まず定期的に来るもの — 記事3行が順に上がってくるダイジェスト(01)から始まります。次は買いたくさせる3つ — コードを押してコピーするクーポン(02)、残り時間のバーが右端を軸に縮むタイムセール(03)、置いてきたカートを見せ直すリマインド(04)です。その次は関係を尋ねる3つ — 出席・未定・欠席の3ボタンで招待(05)、登録直後のチェックリスト(06)、星ひとつで満足度を問う設問(07)です。最後の2つは呼び戻しと広がり — 離れている間の数字が転がり上がる再訪メール(08)、紹介コードと3枠中2枠の招待ドット(09)です。舞台色はインク3・イエロー2・オレンジ2・白2で回して明るさを均し、隣り合う2つがまぶしすぎません。購入後に届く領収書・配送・セキュリティ通知はメールテンプレート デザイン 9種が既に扱っており、今回は、売るために先に送るメールの番です。
01ニュースレター ダイジェスト
タイトルの下で記事3行が120ms間隔で下から順に上がり、行ごとに読了時間バッジが付いて何から読むかを選びやすくします。行を押すと既読に変わり、行はループの大半で立ち続けるので、どの瞬間を切り取っても空画面になりません(面積変化10.75%、23フレーム中13フレームが動く)。週刊ニュースレター、コンテンツ配信メールに使います。
// 行が順に上がる — 遅延のある行は backwards がないと0フレームに完成形が一瞬光る
.mk.is-demo .mk__row {
animation-name: nlRise;
animation-duration: 2s;
animation-timing-function: $easing;
animation-iteration-count: infinite;
}
.mk.is-demo .mk__row:nth-child(2) { animation-delay: 120ms; animation-fill-mode: backwards; }
.mk.is-demo .mk__row:nth-child(3) { animation-delay: 240ms; animation-fill-mode: backwards; }
@keyframes nlRise {
0% { opacity: 0; transform: translateY(14px); }
30% { opacity: 1; transform: translateY(0); }
80% { opacity: 1; transform: translateY(0); }
88% { opacity: 0; transform: translateY(8px); }
100% { opacity: 0; transform: translateY(14px); }
}
02割引クーポンメール
破線ボックス内のコードを押すとnavigator.clipboard.writeTextでコピーされ、表示が1フレームで「コピーしました」に変わり、結果はaria-live領域が静かに知らせます。ループではボックスが一度膨らんで戻り、コードの場所を指し示します。セール告知、初回購入クーポンに使います。
coupon.addEventListener('click', function () {
mk.classList.remove('is-demo');
coupon.classList.add('is-copied');
live.textContent = '코드를 복사했어요';
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText('SUMMER30').then(function () {}, function () {
live.textContent = '복사 실패 — 코드를 직접 선택하세요';
coupon.classList.remove('is-copied');
});
}
});
03タイムセール残り時間バー
秒のマスは重ねた3枚の数字をsteps(1, end)で1フレームごとに切り替え — クロスフェードは使わないので半透明の幽霊フレームがありません — 下のバーはtransform-origin: rightを軸に縮んで締切が近づくことを示します。23フレーム中22フレームが動き、全フレームが動くのは08だけです。タイムセール告知、締切間近リマインドに使います。
// 秒の切り替え — 重ねた3枚を steps のカットだけで交換する(交差フェードは禁止)
.mk.is-demo .mk__d1 { animation-name: fsD1; animation-duration: 2s;
animation-timing-function: steps(1, end); animation-iteration-count: infinite; }
.mk.is-demo .mk__d2 { animation-name: fsD2; animation-duration: 2s;
animation-timing-function: steps(1, end); animation-iteration-count: infinite; }
.mk.is-demo .mk__d3 { animation-name: fsD3; animation-duration: 2s;
animation-timing-function: steps(1, end); animation-iteration-count: infinite; }
.mk.is-demo .mk__fill { animation-name: fsBar; animation-duration: 2s;
animation-timing-function: linear; animation-iteration-count: infinite; }
@keyframes fsBar {
0% { transform: scaleX(1); }
78% { transform: scaleX(.16); }
82% { opacity: 0; transform: scaleX(.16); }
84% { opacity: 0; transform: scaleX(1); }
90%, 100% { opacity: 1; transform: scaleX(1); }
}
04カート放棄リマインド
残した商品2行が左から滑り込み、合計が1フレームで0ウォンから41,600ウォンへ切り替わります。個数ボタンは本物で、押すたびにスクリプトが合計を計算し直し、toLocaleStringの桁区切り表記ですぐに追いつきます。滑り込む間の面積変化は8.19%です。ECのカートリマインド、在庫通知に使います。
// 金額表記はページの言語に従う(ko 원 · en ₩ · ja ウォン)
function money(n) {
var lang = document.documentElement.lang || 'ko';
var digits = n.toLocaleString('ko-KR');
if (lang === 'ja') { return digits + 'ウォン'; }
if (lang === 'en') { return '₩' + digits; }
return digits + '원';
}
function recompute() {
var items = document.querySelectorAll('.mk__item');
var sum = 0;
for (var i = 0; i < items.length; i++) {
var qty = parseInt(items[i].querySelector('.mk__n').textContent, 10) || 0;
sum += qty * parseInt(items[i].getAttribute('data-price'), 10);
}
total.textContent = money(sum);
}
05イベント招待・出欠
左の日付カードの横に出席・未定・欠席の3ボタンが並び、ループでは出席ボタンが押された状態へカットで切り替わり、チェックアイコンが跳ね上がります。ボタンは実際に押せ、aria-pressedが選択を伝え、別の1つを押すと前の選択が解除されます。ウェビナー・オフラインイベントの招待、予約確認に使います。
// 出席ボタンのカット — 実クリックは滑らかな is-on 遷移、カットは格子プレビューのループ専用
.mk.is-demo .mk__yes {
animation-name: evYes;
animation-duration: 2s;
animation-timing-function: steps(1, end);
animation-iteration-count: infinite;
}
.mk.is-demo .mk__yes .mk__check {
animation-name: evCheck;
animation-duration: 2s;
animation-timing-function: cubic-bezier(.2, 1.4, .4, 1);
animation-iteration-count: infinite;
}
@keyframes evCheck {
0%, 34% { transform: scale(0); }
44% { transform: scale(1.25); }
52%, 84% { transform: scale(1); }
85%, 100% { transform: scale(0); }
}
06オンボーディング チェックリスト
5マスの進捗バーが3マスまで埋まり、4行目のチェックがstroke-dashoffsetで線に沿って描かれます。行を押すと4マス目が実際に埋まり、aria-valuenowが60から80へ移り、バーはgrid-template-columns: repeat(5, 1fr)で組んであるのでズレなく均等です。登録直後のステップメール、機能紹介シリーズに使います。
.mk__line { stroke-dasharray: 1; stroke-dashoffset: 1; }
.mk__next.is-done .mk__line { stroke-dashoffset: 0; }
// プレビューループ:チェックは8%から38%にかけて線に沿って描かれる
.mk.is-demo .mk__line {
animation-name: obDraw;
animation-duration: 2s;
animation-timing-function: $ease-out;
animation-iteration-count: infinite;
}
@keyframes obDraw {
0%, 8% { stroke-dashoffset: 1; }
38% { stroke-dashoffset: 0; }
84% { stroke-dashoffset: 0; }
86%, 100% { stroke-dashoffset: 1; }
}
07星評価アンケートメール
星5つが左から160ms間隔で埋まって4つ目で止まり、お礼の一行が上がってきて回答がメール内で完結します。星はrole="radiogroup"内のラジオなので画面読み上げにも選択肢として読まれ、押すと左まで色が埋まり、一文が点数に変わります。購入後の満足度調査、NPS収集メールに使います。
.mk.is-demo .mk__star:nth-child(1) { animation-name: svFill; animation-duration: 2s;
animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-delay: 120ms; }
.mk.is-demo .mk__star:nth-child(2) { animation-name: svFill; animation-duration: 2s;
animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-delay: 280ms; }
.mk.is-demo .mk__star:nth-child(3) { animation-name: svFill; animation-duration: 2s;
animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-delay: 440ms; }
.mk.is-demo .mk__star:nth-child(4) { animation-name: svFill; animation-duration: 2s;
animation-timing-function: steps(1, end); animation-iteration-count: infinite; animation-delay: 600ms; }
@keyframes svFill {
0% { color: rgba(23,20,26,.22); }
30%, 84% { color: $stage-orange; }
86%, 100% { color: rgba(23,20,26,.22); }
}
08休眠顧客の呼び戻しメール
離れている間にたまった数字3マスがrequestAnimationFrameで0から実際の値まで転がり上がり(1.1秒)、下のボタンが浮かびます。キャプチャ23フレームすべてが動くのは数字が転がり続けるためで、システムが暗い表示を好む場合はprefers-color-schemeブロックがカードを自動でひっくり返し、数字色も空色へ明るくします。3か月未訪問ユーザーの再活性化、購読更新案内に使います。
var counters = document.querySelectorAll('.mk__num');
var DURATION = 1100;
for (var c = 0; c < counters.length; c++) {
counters[c].setAttribute('data-start', String(performance.now()));
}
function rollNumber(now) {
var allDone = true;
for (var i = 0; i < counters.length; i++) {
var el = counters[i];
var target = parseInt(el.getAttribute('data-target'), 10);
var started = parseFloat(el.getAttribute('data-start'));
var t = Math.min(1, (now - started) / DURATION);
var eased = 1 - Math.pow(1 - t, 3);
el.textContent = Math.round(target * eased).toLocaleString('ko-KR');
if (t < 1) { allDone = false; }
}
if (!allDone) { requestAnimationFrame(rollNumber); }
}
requestAnimationFrame(rollNumber);
09友達紹介コードメール
紹介コードのピルの下で点3つが順に埋まり2つ目まで進み、3つ目が空いたまま — あと1人招待すれば達成だと示します。遅延のある点はanimation-fill-mode: backwardsで0フレームの点滅を防ぎ、コードは隣のボタンを押してコピーでき、結果がaria-liveで知らされます。友達紹介キャンペーン、特典進捗の案内に使います。
// 点2つが順に埋まる — ピルの字間を広く取ってコードが一目で読める
.mk.is-demo .d1 { animation-name: rfDot; animation-duration: 2s;
animation-timing-function: $ease-pop; animation-iteration-count: infinite;
animation-delay: 160ms; animation-fill-mode: backwards; }
.mk.is-demo .d2 { animation-name: rfDot; animation-duration: 2s;
animation-timing-function: $ease-pop; animation-iteration-count: infinite;
animation-delay: 380ms; animation-fill-mode: backwards; }
@keyframes rfDot {
0% { transform: scale(0); background: transparent; border-color: rgba(23,20,26,.2); }
18%, 84% { transform: scale(1); background: #5b3fc4; border-color: #5b3fc4; }
88%, 100% { transform: scale(0); background: transparent; border-color: rgba(23,20,26,.2); }
}
どこで壊れるか — 落とし穴
この分野が壊れるのはブラウザではなくメールアプリです。ループをそのまま本文へ送ることはできません。caniemail の実測(2026-04-07版)ではCSSアニメーションを完全に支えるメール環境は24.39%、部分的サポートを合わせても31.71%だからです。そこで9つすべて、骨組みを先に静的に完成させました — プレビューループ(.is-demo)を外した状態でも数字・合計・チェック・ボタンが全部そこにあり、格子カードで回るループはその上に重ねたウェブ用の演出です。メールとして送るのはこの静的状態そのもので、ループが生きるのはウェブ版の案内ページやランディングだけです。支える側の中でも環境ごとに割れます — 1&1のウェブメールは%キーフレームを読めず、LaPosteとSFRは@keyframesに接頭を付け損なうと同じページに記録されているので、キーフレームはfrom/toだけにしておくのが安い防御です。ダークモードはさらに厳しく、prefers-color-scheme のサポートは41.86%で、Yahoo!メールとAOLはこのクエリを丸ごと消し、新しいOutlook系は自前のデータ属性(data-ogscなど)で代えてきます。だから08は明るいカードを基本に置き、暗いブロックの中でのみ数字色を空色(8.07:1)へ持ち上げています。
2つ目は、スクリプトが書き戻す文字でした。04の合計は最初toLocaleString('ko-KR') + '원'ひとつで組みましたが、ja・en のページの文言辞書が画面の文字を差し替えても、個数ボタンを押した瞬間に計算し直された韓国語の「원」が戻ってきます。辞書の差し替えは最初の1回きりで、以降の再計算はスクリプトの仕事だからです。document.documentElement.langを読んで表記(원·₩·ウォン)を切り替える小さなmoney()を置いて初めて、3つの版の合計がそれぞれの言語を守りました。同じ場所で間隔の規則にも引っかかりました — セル間の2pxを勝手に空けたら4pxグリッド検査に掛かり、0に下げて律動を戻しました。メールカードは狭いので、数ピクセルのズレでも表の縦罫の狂いが目に付き、グリッドを守ることがブラウザのページより重要になります。
3つ目は合計のカットです。0ウォンから41,600ウォンへの切替を最初は2枚のラベルをフェードで重ねて行ったところ、中間のフレームすべてに半分重なった幽霊の金額が写りました。steps(1, end)で交換すると状態は2つだけで幽霊は消え、横で滑り込む商品2行と合わせても動くのは23フレーム中17フレームにとどまります。キャプチャ窓が2秒なので、ループ周期はすべて2秒の約数に揃え、戻りは86〜88%の間に収めて、最後のサンプルにはみ出す場面をなくしました。
アクセシビリティ
色は推測ではなく WCAG 2.1 の相対輝度式で直接計算し、通過した組み合わせだけが文字を担います。下の4行が、このパレットで文字として使わない組み合わせです。
| 前景 | 背景 | 実測コントラスト | 判定 |
|---|---|---|---|
| インク #17141a | 白カード #ffffff | 18.24:1 | 合格 |
| インク62%(合成 #6f6d71) | 白カード | 5.12:1 | 合格 |
| 濃紺 #1d4fbb | クーポン地の合成色 #f3f6fe | 6.73:1 | 合格 |
| クリーム #fff7e6 | CTA紺 #2a5fd8(01·04·08の文字) | 5.29:1 | 合格 |
| クリーム #fff7e6 | インク #17141a(カウントダウン数字) | 17.11:1 | 合格 |
| インク #17141a | オレンジ #ff4d1f(CTA・日付カード) | 5.50:1 | 合格 |
| グラス #0f7a52 | 白カード(進捗バー) | 5.35:1 | 合格 |
| クリーム #fff7e6 | グラス #0f7a52(チェック) | 5.02:1 | 合格 |
| 濃紫 #5b3fc4 | 白カード(点)・ピル地の合成 #f5f2ff(コード) | 7.11 · 6.44:1 | 合格 |
| インク #17141a | ライラック #b9a0ff(コピーボタン文字) | 8.28:1 | 合格 |
| 空 #6ec8ff | タイルの合成 #2a262a(ダークモード数字) | 8.07:1 | 合格 |
| クリーム55%合成 | タイルの合成 #2a262a(ダークモードラベル) | 5.28:1 | 合格 |
| オレンジ #ff4d1f | 白カード | 3.32:1 | 塗り星(図形3:1)は合格 · 文字には使わない |
| クリーム #fff7e6 | オレンジ #ff4d1f | 3.11:1 | 不合格 — 文字には使わない |
| ライラック #b9a0ff | 白カード | 2.20:1 | 不合格 — 文字には使わない |
| ミント #4cd4a6 | 白カード | 1.86:1 | 不合格 — サムネイルの飾りにのみ使う |
動きを減らす設定(prefers-reduced-motion: reduce)では、9つすべてが演出だけを止めて値と状態を残します。01は行3つが上がりきったまま立ち、02はボックスが止まってもコードがそのまま読め、03は秒が最初の値で止まりバーは満ちた状態です。04は商品と合計が完成形で並び、05は出席ボタンにチェックが付いたまま止まり、06は4つ目のチェックが描かれたまま止まり、押せば本当に完了します。07は星が灰色に戻って押せば点数が付き、08はカードの浮上だけ止まり数字はスクリプトが最終値まで転がり、09は点2つが埋まった状態で固定されます。9つの完成コードは圧縮ファイルに入っており、開けるのに必要なパスワードは gf6nh9h4 です。
画面読み上げ側の構成です。カードの骨組みはrole="presentation"を付けた表なのでデータ表として読まれず、押すものはすべてボタンなので Enter でも動きます。03の残り時間はrole="timer"とaria-labelの一行で読まれ、重なった数字の層はaria-hiddenです。04の個数ボタンには増やす・減らすそれぞれaria-labelがあり、05の選択はaria-pressed、06のバーはaria-valuenow、07の星はradiogroup内のradioが状態を伝えます。02と09のコピー結果はaria-live領域が静かに知らせ、星とボタンは:focus-visibleのリングでキーボードの位置を示します。サポート範囲の根拠は caniemail のCSSアニメーション調査とprefers-color-scheme 調査、クリップボードは MDN の Clipboard.writeText、コントラスト基準は W3C の最低コントラスト解説に従いました。マーケティングメールの素材はコマース分類へ、購入後に届くメールはメールテンプレート デザイン 9種へ、このサイトがコードをどう検証するかは紹介に書いてあります。
FAQ
メールアプリでもこのアニメーションは動きますか
動きはウェブプレビュー側のものです。caniemail の実測(2026-04-07)ではCSSアニメーションを完全に支えるメール環境は24.39%、部分的サポートを合わせても31.71%なので、送るのは静的状態が正解です。9つすべてループを外しても完成した画面なのでそのままメールになり、ランディングや案内ページでループを生かして見せます。
Outlookでは何が最初に壊れますか
ダークモードです。prefers-color-schemeが届くメール環境は41.86%で、Yahoo!メールとAOLはこのクエリを消し、新しいOutlook系は独自のデータ属性で代えると caniemail に記録があります。色をこのクエリだけに掛けず、明るい側を基本に暗い側を上に重ねる順番が安全です。08がその構造です。
マーケティングメールと取引メールはどう使い分けますか
領収書・配送・セキュリティ通知はメールテンプレート デザイン 9種が、今回は、売るために先に送るニュースレター・クーポン・リマインドが担います。実際のキャンペーンでは01で詰めて02で引き、04で受け止めるように混ぜます。取引メールは情報が先、マーケティングメールはひとつの動作が先という点が構造を分けます。