💬

(実践Cloudflare Workers)MailChannels APIを利用して独自ドメインのメールを送信する

2023/07/20に公開

はじめに

この記事ではCloudflare Workersから独自ドメインのメールを送信する方法について説明します。メールの送信にはMailChannels APIを利用します。

MailChannels APIの使用量は無料です。メールの送信件数に応じて課金されることはありません。また、記事の執筆時点ではCloudflare Workers無料プランと有料プランどちらでも利用できます。

用途

MailChannels APIの用途としては、次のようなものを想定しています。

  • 問い合せフォームの受け付けメール ... (例)「お問い合せを受け付けしました。担当者からの回答をお待ちください。」
  • サインアップした際の仮登録メール ... (例)「登録はまだ完了していません。以下のリンクをクリックして登録を完了してください。」
  • アラート通知メール ... (例)「普段と異なる端末からログインされました。心当たりがない場合はお問い合せください。」

MailChannels APIはメールの送信のみサポートしています。返信をトリガーに処理を実行する、といった機能は提供されていません。

実行環境

記事の投稿にあたり、以下の環境にて動作確認を行いました。

  • wranglerコマンドのバージョン: v3.2.0
  • wrangler.tomlファイルのcompatibility_date: 2023-07-10

なお、記事の内容はmacOSで動作確認を行いました。Windowsでも問題なく動作するはずですが、不具合が発生した場合はWSLなどLinux環境で操作を試してみてください。

システム要件

後ほど説明しますが、DNSの設定をする際にopensslコマンドウィ利用します。記事を読み進める前に最新のopensslコマンドをインストールしてください。

ドメインの制限

MailChannels APIを利用してメールを送信する場合、ドメインはCloudflareで取得したものに限られます。

この記事の目的はMailChannels APIの解説ですので、ドメインを購入または移管する手順については説明しません。といっても、作業は簡単です。次の記事を参考に進めてみてください。

余談になりますが、インターネット上の記事の中には、Cloudflareはドメイン移管のみ対応していると書かれているものがあります。そのような記事は内容が古いのでご注意ください。

記事を執筆している2023年7月現在、Cloudflareでドメインを新規購入できます。安売りしている業者と比較すると割高に感じるかもしれませんが、妥当な価格で販売されています。

さらに余談になりますが、TLDの中には問答無用で迷惑メール判定されるものがあります。メールが届かないトラブルを避けるために、.comや.net`など一般的なドメインの取得をお勧めします。

適切なDNS設定の必要性

Cloudflare公式ブログにはMailChannels APIを利用すれば手軽にメール送信ができると書かれています。ただし、最も重要な項目が省略されています。DNSの設定です。

記事が長くなるのを避けるため説明を省略したものと思われます。しかし、プロダクション環境でアプリケーションを運用するのであれば適切なDNSの設定は必須です。

DNSの設定を省略すると、以下の不都合が生じます。

  1. 迷惑メールとして判定される恐れがある。
  2. メールが届かない恐れがある。
  3. 第三者のなりすましメールを排除できない。

これらの不都合を避けるため、Workerの実装例の後にDNSの設定について詳しく説明します。

実装例

以下はMailChannels APIを利用したメール送信の実装例です。Cron Triggersにより30分ごとにWorkerが起動して、メールが送信されます。

この後、DNSの設定について説明する都合上TypeScriptで実装しています。もちろん、JavaScriptで実装しても問題ありません。

まずはnpm create cloudflare@latestコマンドを実行し、テンプレートはHello Workerを選んでください。その後、生成されたファイルの内容を次のように書き換えてください。

wrangler.toml

name = "sendmail"
main = "src/worker.ts"
compatibility_date = "2023-07-10"
workers_dev = false

[triggers]
crons = ["*/30 * * * *"]

src/worker.ts

export interface Env {
	DKIMPrivateKey: string;
}

interface EmailAddress {
	email: string;
	name?: string;
}

interface Personalization {
	to: [EmailAddress, ...EmailAddress[]];
	from?: EmailAddress;
	dkim_domain?: string;
	dkim_private_key?: string;
	dkim_selector?: string;
	reply_to?: EmailAddress;
	cc?: EmailAddress[];
	bcc?: EmailAddress[];
	subject?: string;
	headers?: Record<string, string>;
}

interface ContentItem {
	type: string;
	value: string;
}

interface MailSendBody {
	personalizations: [Personalization, ...Personalization[]];
	from: EmailAddress;
	reply_to?: EmailAddress;
	subject: string;
	content: [ContentItem, ...ContentItem[]];
	headers?: Record<string, string>;
}

export default {
	async scheduled(event, env, ctx) {
		const toEmailAddress: EmailAddress = {
			email: '送信先のメールアドレス',
		};
		const fromEmailAddress: EmailAddress = {
			email: '送信元のメールアドレス',
			name: '送信元の名前',
		};
		const personalization: Personalization = {
			to: [toEmailAddress],
			from: fromEmailAddress,
			dkim_domain: 'example.com',
			dkim_selector: 'mailchannels',
			dkim_private_key: env.DKIMPrivateKey,
		};
		const now = new Date().getTime();
		const content: ContentItem = {
			type: 'text/plain',
			value: `こんにちは。現在時刻をお知らせします。${now}`,
		};
		const payload = {
			personalizations: [personalization],
			from: fromEmailAddress,
			subject: 'cronmailからのお知らせ',
			content: [content],
		};
		const response = await fetch('https://api.mailchannels.net/tx/v1/send', {
			method: 'POST',
			headers: {
				'content-type': 'application/json',
			},
			body: JSON.stringify(payload),
		});
		console.log('Done', response.status);
	},
};

DNSの設定

ここからは独自ドメインとしてexample.comを取得したと仮定して説明を進めます。

DNSSECの有効化

ドメインを新規購入した場合、DNSSECはデフォルトで無効になっています。すでに設定済みであれば、この手順は読み飛ばしてください。

  1. Cloudflareのダッシュボードを開き、メニューの中のManage Domainsをクリックします。
  2. 取得済みドメインの一覧が表示されます。対象のドメインを見つけたらManageをクリックします。
  3. example.comの詳細画面が表示されます。メニューの中のConfigurationをクリックします。
  4. DNSSECボタンをクリックして有効化します。数分待たされる場合があります。
  5. ターミナルを開いてwhoisコマンドを実行してください。以下のように表示されたら設定完了です。

※Windows環境ではwhoisコマンドがインストールされていないと思いますので、WSLなどLinux環境を利用してください。

whois example.com
...省略...
DNSSEC: signedDelegation

解説

DNSSECが未設定の場合、ネームサーバー間の通信は平文で行われます。通信の改竄や盗聴を防ぐため、特別な理由がない限りDNSSECを有効にしてください。

DNSSEC登場の背景については以下の書籍を参考にしてください。やや古い書籍ですが、DNSSECを含めDNS全般について網羅的に解説されています。

SPFレコードの追加

  1. ドメイン詳細画面のUpdate DNS configurationをクリックします。
  2. Add Recordボタンをクリックします。
  3. 以下の内容でレコードを作成します。入力が完了したらSaveボタンをクリックします。
  • Type: TXT
  • Name: @
  • TTL: Auto
  • Content: v=spf1 include:_spf.mx.cloudflare.net include:relay.mailchannels.net -all
  • Comment: 空欄で構いません。

解説

メールのプロトコルには送信者を認証する仕組みが含まれていません。何も対策しなければ、攻撃者がなりすましメールを送信できてしまいます。そこで、送信元のメールサーバーを宣言するのがSPFレコードです。

例えば、本物のメールサーバーはAWSで稼働しているとします。もし攻撃者がGCPに構築した偽物のメールサーバーからなりすましメールを送信すると、宣言されている送信元と異なるメールサーバーから送信されたと判定されて認証は失敗します。

Domain Lockdown™レコードの追加

  1. ドメイン詳細画面のUpdate DNS configurationをクリックします。
  2. Add Recordボタンをクリックします。
  3. 以下の内容でレコードを作成します。入力が完了したらSaveボタンをクリックします。
  • Type: TXT
  • Name: _mailchannels
  • TTL: Auto
  • Content: v=mc1 cfid=example.workers.dev
  • Comment: 空欄で構いません。

example.workers.devは各自のCloudflareアカウントの値に置き換えてください。確認するにはCloudflareダッシュボードを開き、メニューの中のWorkers and Pagesを選びます。ページの下部に現在設定中の*.workers.devが表示されます。

解説

SPFレコードを設定すれば送信元のメールサーバーを認証できます。しかし、攻撃者もMailChannels APIを利用していた場合、本物か偽物か判定できません。

そこで、MailChannels独自のDomain Lockdown™レコードを設定することで送信元のWorkerを宣言します。これによりWorker単位で送信元を認証できます。

DMARCレコードの追加

  1. ドメイン詳細画面のUpdate DNS configurationをクリックします。
  2. Add Recordボタンをクリックします。
  3. 以下の内容でレコードを作成します。入力が完了したらSaveボタンをクリックします。
  • Type: TXT
  • Name: _dmarc
  • TTL: Auto
  • Content: v=DMARC1; p=reject; pct=100; rua=mailto:レポートの送信先メールアドレス
  • Comment: 空欄で構いません。

解説

DMARCレコードはメール送信者の認証に失敗した際の振る舞いを指定します。Contentに指定した値の意味は次のとおりです。

  • v=DMARC1; ... レコードの内容がDMARCであることを宣言する文字列です。
  • p=reject; ... 認証が失敗した場合、メールを拒否(破棄)することを示します。
  • pct=100; ... 振る舞いを適用する割合をパーセンテージで示します。この例では100パーセント、つまり常に拒否することを示します。
  • rua=mailto:レポートの送信先メールアドレス ... 認証が失敗した旨を報告するメールアドレスを指定します。通常、レポートは1日ごとに送信されます。スパムの被害状況を把握するのに役立ちます。

DKIMレコードの追加

まずは秘密鍵を作成します。以下のコマンドを実行してください。成功するとprivate_key.pemファイルとprivate_key.txtファイルが作成されます。

openssl genrsa 2048 | tee private_key.pem | openssl rsa -outform der | openssl base64 -A > private_key.txt

続いて以下のコマンドを実行してください。このコマンドはWorkerから秘密の環境変数を読み取りできるように設定するためのコマンドです。

cat private_key.txt | npx wrangler secret put DKIMPrivateKey

最後に以下のコマンドを実行します。成功するとdkim_record.txtファイルが作成されます。

echo -e "v=DKIM1;p=" > dkim_record.txt && openssl rsa -in private_key.pem -pubout -outform der | openssl base64 -A >> dkim_record.txt

それでは、Cloudflareのダッシュボードに戻ってレコードを追加しましょう。

  1. ドメイン詳細画面のUpdate DNS configurationをクリックします。
  2. Add Recordボタンをクリックします。
  3. 以下の内容でレコードを作成します。入力が完了したらSaveボタンをクリックします。
  • Type: TXT
  • Name: mailchannels._domainkey
  • TTL: Auto
  • Content: 先ほど生成したdkim_record.txtファイルの値(v=DKIM1; p=...
  • Comment: 空欄で構いません。

解説

DKIMレコードは電子署名を追加するためのレコードです。すでに送信元を認証するためのレコードは追加しましたが、DKIMレコードを追加することでメールの文面も間違いなくドメインの所有者が作成したものであると判定できるようになります。

試運転

準備は整いました。Workerをデプロイしましょう。設定にミスがなければ30分ごとにメールが届きます。

npx wrangler deploy

もしメールが届かない場合はDNSの設定を見直してみてください。また、npx wrangler tailを実行して、エラーが発生していないかログを確認してみてください。

よくある質問と回答

ここからはMailChannels APIを利用する際の疑問にお答えします。

(Q. 1)本当に無料で使える?

はい、信じられないことに無料です。どれだけメールを送信しても一切課金されません。また、送信できる件数は1時間あたり1,000件まで、といったRate / Limitも特に設けていないそうです。

さらに、メールの内容にも制限はないそうです。そのような用途を推奨する意図はありませんが、技術的には広告メールを送信できます。ただし、MailChannelsのスパムフィルターによって送信の段階でリクエストを拒否される可能性はあります。

とはいえリリースされてから日が浅いため、将来的に有料化されたり制限が追加される可能性はありそうです。

(Q. 2)サブドメインからメール送信できる?

サブドメインは利用できません。例えば、メールの送信元としてnoreply@info.example.comのようなサブドメインは指定できません。

(Q. 3)メールの件名や本文にUnicodeは使える?

使えます。実装例のcontentプロパティの値を「こんにちは」などに変更してメール送信を試してみてください。

(Q. 4)メールのサイズに制限はある?

ヘッダーなどもすべて含めて、1件あたりの上限は20 MBです。上限に達していた場合、MailChannels APIのリクエストは失敗します。Base64でメール本文に画像を埋め込む場合などは注意してください。

(Q. 5)MailChannels APIのレスポンスのステータスコードについて知りたい

実装例ではresponse.status === 202とハードコードしていました。以下、ステータスコードの意味についてMailChannels公式ドキュメントから引用します。

  • 200: (成功)dry-runフラグが指定された場合のステータスであり、実際にメールは送信されない。
  • 202: (成功)実際にメールが送信され、送信が成功したことを意味する。
  • 400: (失敗)APIリクエストに誤りがある。
  • 403: (失敗)MailChannels APIの利用が許可されていない。
  • 413: (失敗)メールのサイズが20 MBを越えている。
  • 500: (失敗)MailChannelsのサーバーでトラブルが発生している。

まとめると、通常は202かどうかを判定するだけで問題ないかと思います。丁寧にハンドリングするのであれば400と413も考慮した方がよさそうです。

なお、403エラーが返されるパターンについては今のところ遭遇していません。将来的にMailChannelsが有料化されて、権限のないユーザーからリクエストされた場合のステータスコードとして予約されているのかもしれません。

(Q. 6)メールの受信者側でSPF・DKIM・DMARCが正しく設定されているか確認するには?

メールクライアントによります。ここでは利用者が多いと想定されるWeb版のGmailを例に説明します。

  1. ブラウザを開いてhttps://mail.google.comにアクセスします。
  2. MailChannels APIを利用して送信されたメールを選びます。
  3. メニューの中の「その他」を選びます。
  4. 「メッセージのソースを表示」を選ぶと、メールのソースが表示されます。
  5. SPF・DKIM・DMARCすべてPASSになっていれば正しく設定されています。

(Q. 7)突然「Report domain: example.com Submitter: google.com Report-ID: XXXXXXXX」という件名のメールが届いた。これは何?

安心してください。これはDMARCの報告メールです。DMARCレコードの設定手順にて解説しましたが、送信者を偽装したメールを検知した場合にレポートが作成されます。この報告メールが届いたからと言って、今すぐに対処が必要であるとは限りません。

スパムが届くのはよくあることですから、ある程度の件数が報告されるのはそれほど深刻な問題ではありません。それを踏まえた上で、ある日を境に急激にスパムの件数が増えたのであれば何らかの攻撃を試されている可能性が考えられます。レポートの内容は定期的に確認するようにしましょう。

おわりに

例えば普段の環境とは異なる端末からのログインを検知してメール通知するなど、要件として送信専用でしたらMailChannels APIで十分なケースがほとんどかと思います。実装の観点からもfetch()を呼び出すだけですからお手軽です。

唯一にして最も気になるのが完全無料で無制限という点です。言ってみればCloudflareがMailChannelsにフリーライドしている状態ですので、MailChannelsにメリットがありません。裏でどのような取引があったのか気になります。

この不安さえなければMailChannels APIはメール送信のデファクトスタンダードになりそうです。

参考資料

  1. Send email using Workers with MailChannels - The Cloudflare Blog
  2. Tackling Email Spoofing and Phishing - The Cloudflare Blog
  3. What is a DNS DKIM record? - Cloudflare
  4. MailChannels Pages Plugin - Cloudflare Pages docs
  5. Sending Email from Cloudflare Workers using MailChannels Send API - MailChannels Help Center
  6. Secure your domain name against spoofing with Domain Lockdown™ - MailChannels Help Center
  7. Set up SPF Records - MailChannels Help Center
  8. Transactional API - MailChannels

Discussion