5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTMLメール開発の悪夢を終わらせる:Next.js + React Email + SendGrid で作るモダンなメール配信基盤と、メーラー崩れとの死闘

5
Posted at

Webサービスの開発において、切っても切り離せないのが「メール送信機能」です。
しかし、多くのエンジニアにとってHTMLメール開発はできれば避けて通りたい苦行の筆頭ではないでしょうか。

  • 「20年前のホームページか?」と思うような <table> タグのネスト地獄
  • インラインで地道に書き込むCSS
  • OutlookやGmailアプリで容赦なく崩れるデザイン
  • 本番送信するまで仕上がりがわからない開発フィードバックの遅さ

本記事では、これらの悪夢をすべて過去のものにする Next.js + React Email + SendGrid を組み合わせた「コンポーネント指向」の超高速メール開発環境の構築方法を解説します。
さらに、この記事では実際の運用で直面した「メーラーごとの表示崩れ」とその解決策についても余すことなくシェアします。

メール開発を支える3つの柱

今回構築するアーキテクチャの主役たちです。

  1. React Email: ReactコンポーネントでHTMLメールを記述できるライブラリ。Tailwind CSSも使え、ローカルで爆速のプレビュー環境が立ち上がります。
  2. SendGrid: 高い到達率と信頼性を誇る、世界最大級のメール配信プラットフォーム。豊富なAPIと強力なWebhook機能を備え、本番環境の配信インフラとして盤石の選択肢です。
  3. Next.js (App Router): Route Handlerを使って、React Emailで生成したHTMLをSendGrid API経由で送信するサーバーサイドの配信処理を構築します。

開発・送信のアーキテクチャ

Step 1: React Emailの導入と爆速プレビュー環境の構築

まずは既存のNext.jsプロジェクト、あるいは新規プロジェクトにReact Emailを導入します。

1. インストール

プロジェクトのルートディレクトリで以下のコマンドを実行します。

npm install @react-email/components @react-email/render -S
npm install react-email -D

2. 初期化とプレビューサーバーの起動

package.jsonの scripts にプレビュー用のコマンドを追加します。

{
  "scripts": {
    "dev:email": "email dev -p 3001"
  }
}

コマンドを実行して、プレビュー環境を起動します。

npm run dev:email

ブラウザで http://localhost:3001 にアクセスすると、React Emailのダッシュボードが表示されます。デフォルトで emails ディレクトリが自動作成され、そこにサンプルコードが生成されます。

image.png

Step 2: Reactコンポーネントでメールをデザインする

では、実際にコンポーネントを作成してみましょう。React Emailでは、@react-email/components から提供される専用のラッパーコンポーネントを使用します。また、Tailwind CSSも標準サポートされています。

emails/WelcomeEmail.tsx を以下のように作成します。

import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Img,
  Preview,
  Section,
  Text,
  Tailwind,
} from "@react-email/components";
import * as React from "react";

interface WelcomeEmailProps {
  username: string;
}

export const WelcomeEmail = ({ username = "ゲスト" }: WelcomeEmailProps) => {
  return (
    <Html>
      <Head />
      <Preview>サービスへようこそ!アカウント登録が完了しました</Preview>
      <Tailwind
        config={{
          theme: {
            extend: {
              colors: {
                brand: "#4F46E5",
              },
            },
          },
        }}
      >
        <Body className="bg-gray-50 my-auto mx-auto font-sans">
          <Container className="border border-solid border-[#e8e8e8] rounded my-[40px] mx-auto p-[20px] max-w-[465px] bg-white shadow-sm">
            <Section className="mt-[32px] text-center">
              <Img
                src="https://your-domain.com/static/logo.png" // 本番環境のアセットURL
                width="40"
                height="40"
                alt="Logo"
                className="my-0 mx-auto"
              />
            </Section>
            <Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
              Welcome to <strong>Our Platform</strong>!
            </Heading>
            <Text className="text-black text-[14px] leading-[24px]">
              こんにちは、{username}</Text>
            <Text className="text-gray-600 text-[14px] leading-[24px]">
              アカウントのご登録ありがとうございます。私たちはあなたのビジネスを加速させるための最適なツールを提供します。さっそくダッシュボードにアクセスして始めましょう!
            </Text>
            <Section className="text-center mt-[32px] mb-[32px]">
              <Button
                className="bg-brand rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
                href="https://your-domain.com/dashboard"
              >
                ダッシュボードを開く
              </Button>
            </Section>
            <Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
            <Text className="text-[#666666] text-[12px] leading-[24px]">
              このメールに心当たりがない場合は、お手数ですが無視してください。
            </Text>
          </Container>
        </Body>
      </Tailwind>
    </Html>
  );
};

export default WelcomeEmail;

React Emailの何が革新なのか?

  • Hot Reload: コードを書き換えた瞬間、localhost:3001 のプレビューに即座にで反映されます。
  • Propsの書き換えテスト: ダッシュボードの右側パネルから、username などのPropsをブラウザ上で自由に入力して、表示の変化をテストできます。
  • HTML/プレーンテキスト両対応: 自動的にテキスト版のメールも生成・確認できます。

Step 3: Next.js + SendGrid API で送信APIを構築する

次に、作成したReactコンポーネントを本番環境から送信するサーバーサイドの実装を行います。メール配信には、信頼の SendGrid を使用します。

1. SendGrid SDKのインストール

npm install @sendgrid/mail

2. 環境変数の設定

SendGridダッシュボードでAPIキーを作成し、.env.local に追加します。また、送信元ドメインの認証(Sender Authentication)を事前に済ませておきます。

SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxx

3. Route Handlerの実装 (Next.js)

app/api/send/route.ts を作成します。
React Emailのコンポーネントを @react-email/renderrender 関数を使ってHTML文字列に変換し、それを SendGrid の html プロパティに渡して送信します。

import { NextResponse } from "next/server";
import sgMail from "@sendgrid/mail";
import { render } from "@react-email/render";
import { WelcomeEmail } from "@/emails/WelcomeEmail";
import React from "react";

// APIキーを設定
sgMail.setApiKey(process.env.SENDGRID_API_KEY!);

export async function POST(request: Request) {
  try {
    const { email, username } = await request.json();

    // 1. React Email コンポーネントを HTML 文字列にレンダリング
    const emailHtml = await render(
      React.createElement(WelcomeEmail, { username })
    );

    // 2. SendGridの送信パラメータを設定
    const msg = {
      to: email,
      from: "info@yourdomain.com", // SendGridで認証済みの送信元アドレス
      subject: "アカウント登録完了のお知らせ",
      html: emailHtml,
      // 必要に応じてテキストのフォールバックも設定可能
      text: `こんにちは、${username}様。アカウント登録が完了しました。`,
    };

    // 3. 送信実行
    await sgMail.send(msg);

    return NextResponse.json({ message: "Email sent successfully via SendGrid" });
  } catch (err: any) {
    console.error("SendGrid Error:", err.response?.body || err.message);
    return NextResponse.json(
      { error: err.response?.body || err.message },
      { status: 500 }
    );
  }
}

これでNext.jsとSendGridが完全に繋がりました!React Emailによる最高の開発体験(DX)を維持しながら、本番配信はSendGridの堅牢なインフラに委ねる、理想的な設計です。

運用で直面した「表示崩れ」と、その回避策

「React Emailでローカルプレビューもバッチリだし、これでHTMLメール開発は完全勝利だ!」
...そう思った時期が私にもありました。

いざ本番運用を開始し、様々なデバイス・メーラーで受信テストを行うと、ローカルプレビュー(ブラウザ)では完璧だったデザインが容責なく崩れました。
ここからは、「メーラーごとの表示崩れ」の具体的な原因とハックを共有します。

1. Outlook(Windowsデスクトップ版)の「Wordレンダリングエンジン」

Windowsのデスクトップ版Outlookは、HTMLのレンダリングにブラウザエンジンではなく Microsoft Word を使用しています。このため、CSSの近代的な機能がほぼ全滅します。

  • バグ①: border-radius(角丸)が無視され、ボタンが角張る
    • 対策: Outlookで完全に角丸を再現するには、VML(Vector Markup Language)という太古のマークアップを埋め込む必要があります。しかし複雑になるため、多くの場合は「丸みのない四角いボタンになる仕様」として割り切るか、ボタン自体を「画像」として配置するかのトレードオフになります。
  • バグ②: flexgrid が一切効かない
    • 対策: 2カラムレイアウト(画像を左、説明文を右)などを組む際、安易に flex を使うと縦並びになって崩れます。React Emailの <Section><Column> コンポーネントを使用してください。これは内部的にクラシックな <td> テーブル構造を出力するため、Outlookでも正しく横並びになります。
// ❌ flexを使うとOutlookで崩れる
<div className="flex justify-between">
  <div>左側のコンテンツ</div>
  <div>右側のコンテンツ</div>
</div>

// ⭕ Columnコンポーネントを使う(テーブルタグにコンパイルされる)
<Section>
  <Column>左側のコンテンツ</Column>
  <Column>右側のコンテンツ</Column>
</Section>

2. Gmail(特にモバイルアプリ)の「Tailwind クラス一部無視」問題

Gmailはセキュリティ上の理由から、受信したHTMLメールの <style> タグやインラインCSSをかなり強力にクレンジング(削除・変更)します。

  • バグ①: Tailwindの my-automx-auto (マージン自動)が効かない
    • Gmailでは、margin: 0 auto; による要素の中央寄せが高確率で無視され、メール全体が左寄せになってしまいます。
    • 対策: 外側のコンポーネントで明示的にピクセル指定のマージン(例: margin-left: auto; margin-right: auto;)をインラインスタイルで指定するか、<Container> の幅を固定してテーブル要素の align="center" を使います。
  • バグ②: Webフォント(Google Fontsなど)が一切適用されない
    • 対策: GmailはWebフォントの読み込みを拒絶します。そのため、必ず標準のシステムフォント(サンセリフ体など)を第一フォールバックに指定してください。
    // フォントファミリーは安全なシステムフォントスタックを必ず指定する
    const fontSans = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
    

3. Dark Mode(ダークモード)時の「背景・文字色の強制自動反転」

iOS MailやGmailなどのモダンなメーラーには、OSがダークモードの際に「メールの色を自動で反転させる」機能があります。これがデザインを最悪にします。

  • バグ: 白いロゴ画像が、背景が黒になったことで見えなくなる
  • 対策:
    1. 透過PNG + 境界線(アウトライン)の付与: ロゴ画像は必ず背景を透過したPNGにし、ロゴの文字自体に薄い白やグレーのアウトライン(光彩)をつけておくことで、背景が黒になっても視認性を維持できるようにします。
    2. メタタグによるダークモードの明示的な制御:
      メールの <Head> にダークモード用のメタタグを埋め込み、自動反転を部分的に制御します(※ただし、すべてのメーラーで完全に制御できるわけではありません)。

4. 画像アセットの「ホットリンク無効」とCORSキャッシュ

メール内の <img> に指定する画像URLは、絶対パス(https://...)である必要があります。

  • バグ: 開発用・テスト用のローカルURLのまま送信してしまい、本番で画像が表示されない
  • 対策:
    画像は Vercel Blob、Cloudflare R2、または AWS S3 などの信頼できるCDN(本番環境のオブジェクトストレージ)にアップロードし、その絶対パスを使用します。また、メーラーによってはプロキシ経由で画像を表示するため、CORSの設定で画像へのGETアクセスがパブリックに許可されていることを確認してください。

なぜ本番配信に SendGrid を選ぶべきなのか?

開発体験(DX)を React Email で劇的に向上させた後、「本番運用の安定性」を支えるのが SendGrid です。実際に運用して感じたSendGridの強みを共有します。

  1. 極めて強固なドメイン認証(SPF/DKIM/DMARC)の設定と維持:
    SendGridのダッシュボードに表示されるCNAMEレコードをDNSに追加するだけで、自動的にDKIM署名やSPFが適切に設定されます。これにより、2024年以降に厳格化されたGmailの「送信者ガイドライン」も簡単にクリアできます。
  2. Event Webhookによる「バウンス(不達)ログ」の自動ハンドリング:
    メールが届かなかった際(一時的・永続的バウンス)、SendGridは即座にWebhookでイベントをNext.jsのAPIエンドポイントに通知してくれます。これを受け取り、DBのユーザーのメールステータスを自動更新することで、届かないアドレスにメールを送り続けてレピュテーション(ドメインの信頼性)が低下するのを防ぐことができます。
  3. アクティビティフィードによる配信追跡の容易さ:
    「ユーザーからメールが届かないと言われた」際、SendGridの管理画面から送信先アドレスで検索するだけで、送信完了・配信完了・またはエラーになった理由(バウンスなど)が秒単位で確認できます。

まとめ:Next.js + React Email + SendGrid は現代の最強スタック

表示崩れとの格闘は依然として存在しますが、それでも従来の「ローカルにHTMLファイルを置いて、手動でテスト送信を繰り返す」手法に比べれば、開発スピードと堅牢性は10倍以上に向上します。

メリットのおさらい

  1. TypeScriptの恩恵: メール送信時の変数(Props)に型定義があるため、「本番で名前の埋め込みが漏れて空白になっていた」というバグがコンパイル段階で防げます。
  2. プレビュー環境でのデザイン品質: ブラウザ幅での見え方や、テキスト抽出時の内容が即座に確認できます。
  3. SendGridの圧倒的な堅牢性: 送信信頼性、認証の手軽さ、ログハンドリングの容易さは他の追随を許しません。

これから新しくメール送信機能を実装する方、あるいは古いHTMLテンプレートの維持管理に限界を感じている方は、ぜひこのモダンスタック(Next.js + React Email + SendGrid)に挑戦してみてください。あなたの開発体験が劇的に変わること間違いありません。

この記事が役に立ちましたら、ぜひ「いいね」と「ストック」をお願いします!メール開発の苦労話や「こんなハックもあるよ!」という知見があれば、コメント欄で教えていただけると嬉しいです。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?