Site icon image rpine lab Blog

プログラミングや電子工作系の記事を投稿しています。

📬 ResendとNext.js Server Actionsでメール送信機能を実装する

はじめに

この記事では、Resendというメール送信APIサービスを利用してNext.jsアプリケーションにメール送信機能を実装する方法を解説します。

このサービスを選択した背景としては、Cloudflare Workersで使っていたMailChannelsが無料で使えなくなったため、代替サービスを探していたということがあります。Resendは小規模開発で十分な無料枠があるため、これに移行することにしました。

Resendとは

Resendは開発者体験に重きが置かれたメールを送信APIサービスです。SendGridやAmazon SESなどの既存サービスと比べると後発であるため色々と使いやすくなっています。

  • 色々な言語のSDKが用意されていてドキュメントが充実
  • 様々な言語とフレームワークに対応QuickStartガイド
  • 無料プランで3,000通/月・100通/日まで送信可能(2024年12月現在)
Image in a image block
Resendの管理画面
Image in a image block
Resendの管理画面 2

アーキテクチャ概要

今回は、ResendとNext.jsのServer Actionsを組み合わせて、フォームに入力された内容をメールで送信するサイトを構築します。

今回構築するサイトの構成は以下のとおりです。メールを送信するためのサーバーサイドもNext.js のServer Actionsを使って実現します。

💡
現在、Resendのメール作成ライブラリ(@react-email)と Next.js 15+React 19の組み合わせにおいて、Edgeランタイム(Server Actionsで利用)上で正常に動かない問題があるため、Next.js 14を使用します。(関連Issue
  • フロントエンド:Next.js 14 (App Router) + React 18
  • バックエンド:Next.js 14 (Server Actions)
Image in a image block
アーキテクチャ

環境構築

Resendのアカウント作成とAPIキーの取得

  1. Resendのサインアップページでアカウントを作成します。メールアドレス+パスワードの他に、GitHubやGoogleアカウントでのサインアップが可能です。
  2. 管理画面のDomainsで、送信元アドレスに使うドメインを登録します。登録後、表示されたDNSレコードをドメインのDNSに追加します。
    Image in a image block
    ドメイン設定(サンプル)
  3. 管理画面のAPI KeysでAPIキーを生成します。PermissionはSending Accessを選択します。後で環境変数に入れて使うので、表示されたAPIキーをメモしておきます。
    Image in a image block
    APIキー作成

Next.jsプロジェクトのセットアップ

  1. create-next-appコマンドでNext.js 14のプロジェクトを新規作成します。
    npx create-next-app@14 resend-nextjs
    
    ✔ Would you like to use TypeScript? … No / Yes
    ✔ Would you like to use ESLint? … No / Yes
    ✔ Would you like to use Tailwind CSS? … No / Yes
    ✔ Would you like to use `src/` directory? … No / Yes
    ✔ Would you like to use App Router? (recommended) … No / Yes
    ✔ Would you like to customize the default import alias (@/*)? … No / Yes
  2. 初期テンプレートのグローバルCSSスタイルを削除します。
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    
    /* これより下を全部削除  */
    src/app/globals.css
  3. 公式SDK(resend)をインストールします。
    cd resend-nextjs
    pnpm add resend
  4. .env.development.localを作成し、先ほどメモしたResendのAPIキーを環境変数に設定します。
    RESEND_API_KEY=re_123456789
    .env.development.local(APIキーをre_123456789とした例)

Server Actionsの実装(バックエンド)

メールテンプレートの作成

メール本文のテンプレートを作成します。このように、Resend APIではHTMLメールの本文をJSX(tsx)構文でReactコンポーネントとして作成でき、styleを使った装飾なども可能です。

import { Fragment } from 'react';

interface EmailTemplateProps {
  name: string;
  message: string;
}

export const EmailTemplate: React.FC<Readonly<EmailTemplateProps>> = ({
  name,
  message,
}) => (
  <div style={{ color: '#333' }}>
    <p>以下の内容でお問い合わせを受信しました。</p>
    <p>
      <strong>お名前:</strong>
    </p>
    <p
      style={{
        margin: '1em 0',
        padding: '0.5em',
        border: '1px solid #ccc',
        backgroundColor: '#f9f9f9',
      }}
    >
      {name}
    </p>
    <p>
      <strong>お問い合わせ内容:</strong>
    </p>
    <p
      style={{
        margin: '1em 0',
        padding: '0.5em',
        border: '1px solid #ccc',
        backgroundColor: '#f9f9f9',
      }}
    >
      {/* 改行を<br />に変換 */}
      {message.split(/\r?\n/g).map((line, index) => (
        <Fragment key={index}>
          {line}
          <br />
        </Fragment>
      ))}
    </p>
  </div>
);
src/components/EmailTemplate.tsx

Resend APIを使ったメール送信関数の作成

Resend APIを使ってメールを送信する関数を作成します。APIキーは設定した環境変数から取得します。

宛先にResendのテスト用アドレスを指定しているため、送ったメールは管理画面でのみ確認ができます。必要に応じて自分のメールアドレスなどに変更してください。

import 'server-only'; // サーバーサイドのみで実行することを明示(任意)

import { Resend } from 'resend';

import { EmailTemplate } from '@/components/EmailTemplate';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendEmail({
  name,
  message,
}: {
  readonly name: string;
  readonly message: string;
}) {
  try {
    const { error } = await resend.emails.send({
      from: 'Acme <[email protected]>',
      to: ['[email protected]'],
      subject: 'お問い合わせ',
      react: EmailTemplate({ name, message }),
    });

    if (error) {
      console.error('Failed to send email', error);
      throw new Error('Failed to send email');
    }
  } catch (error) {
    console.error('Failed to send email', error);
    throw new Error('Failed to send email');
  }
}
src/lib/send-mail.tsx

Server Action関数の作成

Server Actions関数を実装します。Server Actionsはサーバーサイドで実行されるので、Resend APIを使ったメール送信が可能です。実運用ではformDataに対してバリデーションやBot検証を行う必要があります。

'use server';

import { sendEmail } from '@/lib/send-mail';

export async function submit(formData: FormData) {
  const name = formData.get('name')!.toString();
  const message = formData.get('message')!.toString();

  await sendEmail({ name, message });
}
src/app/actions.ts

フォームコンポーネントの作成

入力フォームの作成

入力フォームを作成します。ステート(useFormStatus)を使うのでフォームはClient Componentで作成します。スタイルはTailwindCSSで作成しています。formタグのactions内で作成したServer Actions関数(submit)を呼ぶことで、サーバーサイドでメール送信処理が走ります。

import ContactForm from './ContactForm';

export default function Home() {
  return (
    <main className="flex flex-row justify-center p-8">
      <ContactForm />
    </main>
  );
}
src/app/page.tsx
'use client';

import { useFormStatus } from 'react-dom';
import { submit } from './actions';

export default function ContactForm() {
  return (
    <form
      className="w-full max-w-md"
      action={async (formData) => {
        await submit(formData);
        alert('お問い合わせを受け付けました。');
      }}
    >
      <div className="mb-4">
        <label htmlFor="name" className="text-sm font-medium">
          お名前
        </label>
        <input
          type="text"
          id="name"
          name="name"
          className="mt-1 w-full rounded-md border border-gray-300 p-2"
          required
        />
      </div>
      <div className="mb-4">
        <label htmlFor="message" className="text-sm font-medium">
          お問い合わせ内容
        </label>
        <textarea
          id="message"
          name="message"
          className="mt-1 w-full rounded-md border border-gray-300 p-2"
          rows={10}
          required
        />
      </div>
      <SubmitButton />
    </form>
  );
}

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button
      type="submit"
      className="rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 disabled:bg-gray-400"
      disabled={pending}
    >
      {pending ? '送信中…' : '送信'}
    </button>
  );
}
src/app/ContactForm.tsx

動作確認

npm run devで開発サーバを立ち上げて、フォームにテストデータを入力して送信してみます。

Image in a image block
作成したフォーム

フォームから正常に送信できたら、Resendの管理画面で送信したメールを確認できるはずです。もし宛先をテスト用アドレスから変更していれば、そのアドレスにメールが送られてきているはずです。

Image in a image block
Resendの管理画面で確認した送信メール

まとめ

Resendを利用したNext.jsでのメール送信機能の実装について解説しました。

個人的に思ったResendの利点を:

  • ドキュメントが豊富で、SDKが使いやすい
  • React (JSX構文) でHTMLメールが簡単に作成できる
  • 小規模であれば十分使える無料枠

もう少し複雑なメール本文を作成したければ、同じくResend社が開発しているreact-emailライブラリを使うことでTailwindCSSや既成のコンポーネントを使った高度なメールが容易に作成できます。

参考サイト

広告