Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

メールエージェント

最終更新 Markdown で表示Agent セットアップ

エージェントは Cloudflare Email Service でメールを送受信できます。このガイドでは、Workers バインディングで送信メールを送り、受信メールを Agent へルーティングし、後続の返信を安全に扱う方法を示します。

前提条件

エージェントでメールを使う前に、次が必要です。

  1. Cloudflare Email Service にオンボードしたドメイン。
  2. 送信メール用の wrangler.jsonc 内の send_email バインディング。
  3. 受信メールを Worker へ送る Email Service のルーティングルール。
  4. 任意: 安全な返信ルーティングが必要な場合の EMAIL_SECRET シークレット。

ドメインのセットアップ

  1. Cloudflare Dashboard にログインします。
  2. Compute & AI > Email Service を開きます。
  3. Onboard Domain を選び、ドメインを選択します。
  4. 送信を許可するため、DNS レコード(SPF と DKIM)を追加します。

DNS の変更は、Cloudflare DNS を使うドメインでは通常 5〜15 分で完了します。世界中への伝播には最大 24 時間かかることがあります。

Wrangler の設定

Worker にメールバインディングを追加します。

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "send_email": [
    {
      "name": "EMAIL",
      "remote": true
    }
  ]
}
[[send_email]]
name = "EMAIL"
remote = true

remote = true を指定すると、wrangler dev でのローカル開発中に実際の Email Service API を呼び出せます。

クイックスタート

import { Agent, callable, routeAgentEmail } from "agents";
import { createAddressBasedEmailResolver } from "agents/email";
import PostalMime from "postal-mime";

export class EmailAgent extends Agent {
	@callable()
	async sendWelcomeEmail(to) {
		await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: "[email protected]",
			replyTo: "[email protected]",
			subject: "Welcome to our service",
			text: "Thanks for signing up. Reply to this email if you need help.",
		});
	}

	async onEmail(email) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		console.log("Received email from:", email.from);
		console.log("Subject:", parsed.subject);

		await this.replyToEmail(email, {
			fromName: "Support Agent",
			body: "Thanks for your email! We received it.",
		});
	}
}

export default {
	async email(message, env) {
		await routeAgentEmail(message, env, {
			resolver: createAddressBasedEmailResolver("EmailAgent"),
		});
	},
};
import { Agent, callable, routeAgentEmail } from "agents";
import { createAddressBasedEmailResolver, type AgentEmail } from "agents/email";
import PostalMime from "postal-mime";

export class EmailAgent extends Agent {
	@callable()
	async sendWelcomeEmail(to: string) {
		await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: "[email protected]",
			replyTo: "[email protected]",
			subject: "Welcome to our service",
			text: "Thanks for signing up. Reply to this email if you need help.",
		});
	}

	async onEmail(email: AgentEmail) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		console.log("Received email from:", email.from);
		console.log("Subject:", parsed.subject);

		await this.replyToEmail(email, {
			fromName: "Support Agent",
			body: "Thanks for your email! We received it.",
		});
	}
}

export default {
	async email(message, env) {
		await routeAgentEmail(message, env, {
			resolver: createAddressBasedEmailResolver("EmailAgent"),
		});
	},
} satisfies ExportedHandler<Env>;

送信メール

sendEmail() を使う

sendEmail() は、明示的に渡した send_email バインディング経由で送信メールを送ります。すべてのメッセージにエージェントルーティングヘッダー(X-Agent-NameX-Agent-ID)を自動で注入します。任意で HMAC-SHA256 により署名し、返信を同じエージェントインスタンスへ戻せます。

class MyAgent extends Agent {
	@callable()
	async sendReceipt(to, orderId) {
		const result = await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: { email: "[email protected]", name: "Billing Bot" },
			replyTo: "[email protected]",
			subject: `Receipt for order ${orderId}`,
			text: `Your receipt for order ${orderId} is ready.`,
			secret: this.env.EMAIL_SECRET,
		});

		return result.messageId;
	}
}
class MyAgent extends Agent {
	@callable()
	async sendReceipt(to: string, orderId: string) {
		const result = await this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: { email: "[email protected]", name: "Billing Bot" },
			replyTo: "[email protected]",
			subject: `Receipt for order ${orderId}`,
			text: `Your receipt for order ${orderId} is ready.`,
			secret: this.env.EMAIL_SECRET,
		});

		return result.messageId;
	}
}

secret を渡すと、エージェントはルーティングヘッダーに署名します。createSecureReplyEmailResolver が検証した返信は、同じエージェントインスタンスへ戻ります。

受信者が同じエージェントと会話を続けられるようにしたいときは、replyTo を Worker へ戻るメールボックスに設定します。

受信メールのルーティング

resolver は、受信メールをどの Agent インスタンスが受け取るかを決めます。用途に合う resolver を選びます。

Email Service の基本的な送受信だけなら、createAddressBasedEmailResolver() で十分です。次の安全な返信 resolver は任意で、Agents SDK の返信署名に固有です。Email Service 自体の必須要件ではありません。

createAddressBasedEmailResolver

受信メールに推奨します。宛先アドレスに基づいてメールをルーティングします。

import { createAddressBasedEmailResolver } from "agents/email";

const resolver = createAddressBasedEmailResolver("EmailAgent");
import { createAddressBasedEmailResolver } from "agents/email";

const resolver = createAddressBasedEmailResolver("EmailAgent");

ルーティングのロジック:

宛先アドレス Agent 名 Agent ID
[email protected] EmailAgent(デフォルト) support
[email protected] EmailAgent(デフォルト) sales
[email protected] NotificationAgent user123

サブアドレス形式(agent+id@domain)を使うと、1 つのメールドメインから異なるエージェント名前空間とインスタンスへルーティングできます。

createSecureReplyEmailResolver

署名検証付きの返信フロー向けです。受信メールが送信メールへの本物の返信であることを検証し、攻撃者が任意のエージェントインスタンスへメールをルーティングするのを防ぎます。

import { createSecureReplyEmailResolver } from "agents/email";

const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);
import { createSecureReplyEmailResolver } from "agents/email";

const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET);

エージェントが replyToEmail() または sendEmail()secret でメールを送ると、タイムスタンプ付きでルーティングヘッダーに署名します。返信が戻ると、この resolver は署名を検証し、期限切れでないことを確認してからルーティングします。

オプション:

const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
	// Maximum age of signature in seconds (default: 30 days)
	maxAge: 7 * 24 * 60 * 60, // 7 days

	// Callback for logging/debugging signature failures
	onInvalidSignature: (email, reason) => {
		console.warn(`Invalid signature from ${email.from}: ${reason}`);
		// reason can be: "missing_headers", "expired", "invalid", "malformed_timestamp"
	},
});
const resolver = createSecureReplyEmailResolver(env.EMAIL_SECRET, {
	// Maximum age of signature in seconds (default: 30 days)
	maxAge: 7 * 24 * 60 * 60, // 7 days

	// Callback for logging/debugging signature failures
	onInvalidSignature: (email, reason) => {
		console.warn(`Invalid signature from ${email.from}: ${reason}`);
		// reason can be: "missing_headers", "expired", "invalid", "malformed_timestamp"
	},
});

使うとき: エージェントがメール会話を開始し、返信を同じエージェントインスタンスへ安全に戻したい場合。

createCatchAllEmailResolver

単一インスタンスへのルーティング向けです。宛先アドレスに関係なく、すべてのメールを特定のエージェントインスタンスへ送ります。

import { createCatchAllEmailResolver } from "agents/email";

const resolver = createCatchAllEmailResolver("EmailAgent", "default");
import { createCatchAllEmailResolver } from "agents/email";

const resolver = createCatchAllEmailResolver("EmailAgent", "default");

使うとき: すべてのメールを 1 つのエージェントインスタンスが扱う場合(共有受信箱など)。

resolver の組み合わせ

異なるシナリオを扱うため、resolver を組み合わせられます。

export default {
	async email(message, env) {
		const secureReplyResolver = createSecureReplyEmailResolver(
			env.EMAIL_SECRET,
		);
		const addressResolver = createAddressBasedEmailResolver("EmailAgent");

		await routeAgentEmail(message, env, {
			resolver: async (email, env) => {
				// First, check if this is a signed reply
				const replyRouting = await secureReplyResolver(email, env);
				if (replyRouting) return replyRouting;

				// Otherwise, route based on recipient address
				return addressResolver(email, env);
			},

			// Handle emails that do not match any routing rule
			onNoRoute: (email) => {
				console.warn(`No route found for email from ${email.from}`);
				email.setReject("Unknown recipient");
			},
		});
	},
};
export default {
	async email(message, env) {
		const secureReplyResolver = createSecureReplyEmailResolver(
			env.EMAIL_SECRET,
		);
		const addressResolver = createAddressBasedEmailResolver("EmailAgent");

		await routeAgentEmail(message, env, {
			resolver: async (email, env) => {
				// First, check if this is a signed reply
				const replyRouting = await secureReplyResolver(email, env);
				if (replyRouting) return replyRouting;

				// Otherwise, route based on recipient address
				return addressResolver(email, env);
			},

			// Handle emails that do not match any routing rule
			onNoRoute: (email) => {
				console.warn(`No route found for email from ${email.from}`);
				email.setReject("Unknown recipient");
			},
		});
	},
} satisfies ExportedHandler<Env>;

Agent でのメール処理

AgentEmail インターフェース

エージェントの onEmail メソッドが呼ばれると、AgentEmail オブジェクトを受け取ります。

type AgentEmail = {
	from: string; // Sender's email address
	to: string; // Recipient's email address
	headers: Headers; // Email headers (subject, message-id, etc.)
	rawSize: number; // Size of the raw email in bytes

	getRaw(): Promise<Uint8Array>; // Get the full raw email content
	reply(options): Promise<void>; // Send a reply
	forward(rcptTo, headers?): Promise<void>; // Forward the email
	setReject(reason): void; // Reject the email with a reason
};

メール本文の解析

生メールの解析には postal-mime のようなライブラリを使います。

import PostalMime from "postal-mime";

class MyAgent extends Agent {
	async onEmail(email) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		console.log("Subject:", parsed.subject);
		console.log("Text body:", parsed.text);
		console.log("HTML body:", parsed.html);
		console.log("Attachments:", parsed.attachments);
	}
}
import PostalMime from "postal-mime";

class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		console.log("Subject:", parsed.subject);
		console.log("Text body:", parsed.text);
		console.log("HTML body:", parsed.html);
		console.log("Attachments:", parsed.attachments);
	}
}

自動返信メールの検出

メールループを避けるため、isAutoReplyEmail() で自動返信メールを検出します。

import { isAutoReplyEmail } from "agents/email";
import PostalMime from "postal-mime";

class MyAgent extends Agent {
	async onEmail(email) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		// Detect auto-reply emails to avoid sending duplicate responses
		if (isAutoReplyEmail(parsed.headers)) {
			console.log("Skipping auto-reply email");
			return;
		}

		// Process the email...
	}
}
import { isAutoReplyEmail } from "agents/email";
import PostalMime from "postal-mime";

class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		// Detect auto-reply emails to avoid sending duplicate responses
		if (isAutoReplyEmail(parsed.headers)) {
			console.log("Skipping auto-reply email");
			return;
		}

		// Process the email...
	}
}

これは、メールが自動返信であることを示す標準の RFC 3834 ヘッダー(Auto-SubmittedX-Auto-Response-SuppressPrecedence)を確認します。

メールへの返信

受信メールの返信チャネル経由で返信するには、this.replyToEmail() を使います。

class MyAgent extends Agent {
	async onEmail(email) {
		await this.replyToEmail(email, {
			fromName: "Support Bot", // Display name for the sender
			subject: "Re: Your inquiry", // Optional, defaults to "Re: "
			body: "Thanks for contacting us!", // Email body
			contentType: "text/plain", // Optional, defaults to "text/plain"
			headers: {
				// Optional custom headers
				"X-Custom-Header": "value",
			},
			secret: this.env.EMAIL_SECRET, // Optional, signs headers for secure reply routing
		});
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		await this.replyToEmail(email, {
			fromName: "Support Bot", // Display name for the sender
			subject: "Re: Your inquiry", // Optional, defaults to "Re: "
			body: "Thanks for contacting us!", // Email body
			contentType: "text/plain", // Optional, defaults to "text/plain"
			headers: {
				// Optional custom headers
				"X-Custom-Header": "value",
			},
			secret: this.env.EMAIL_SECRET, // Optional, signs headers for secure reply routing
		});
	}
}

遅延返信

replyToEmail() は生きた AgentEmail オブジェクトが必要なため、onEmail() の中でのみ使えます。あとから返信したい場合(スケジュールタスク、callable メソッド、human-in-the-loop の承認後など)は、送信者情報を状態に保存し、sendEmail() を使います。

class MyAgent extends Agent {
	async onEmail(email) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		this.setState({
			...this.state,
			pendingReply: {
				to: email.from,
				messageId: parsed.messageId,
				subject: parsed.subject,
			},
		});
	}

	@callable()
	async sendDelayedReply(body) {
		const { pendingReply } = this.state;
		if (!pendingReply) return;

		await this.sendEmail({
			binding: this.env.EMAIL,
			to: pendingReply.to,
			from: "[email protected]",
			subject: `Re: ${pendingReply.subject}`,
			text: body,
			inReplyTo: pendingReply.messageId,
			secret: this.env.EMAIL_SECRET,
		});
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		this.setState({
			...this.state,
			pendingReply: {
				to: email.from,
				messageId: parsed.messageId,
				subject: parsed.subject,
			},
		});
	}

	@callable()
	async sendDelayedReply(body: string) {
		const { pendingReply } = this.state;
		if (!pendingReply) return;

		await this.sendEmail({
			binding: this.env.EMAIL,
			to: pendingReply.to,
			from: "[email protected]",
			subject: `Re: ${pendingReply.subject}`,
			text: body,
			inReplyTo: pendingReply.messageId,
			secret: this.env.EMAIL_SECRET,
		});
	}
}

inReplyTo フィールドは In-Reply-To ヘッダーを設定し、メールクライアントがスレッドを正しく組み立てます。secret はエージェントルーティングヘッダーに署名するため、後続の返信は createSecureReplyEmailResolver 経由でこのエージェントインスタンスへ戻ります。

メールの転送

class MyAgent extends Agent {
	async onEmail(email) {
		await email.forward("[email protected]");
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		await email.forward("[email protected]");
	}
}

メールの拒否

class MyAgent extends Agent {
	async onEmail(email) {
		if (isSpam(email)) {
			email.setReject("Message rejected as spam");
			return;
		}
		// Process the email...
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		if (isSpam(email)) {
			email.setReject("Message rejected as spam");
			return;
		}
		// Process the email...
	}
}

エラー処理

sendEmail() または replyToEmail() でメールを送るときは、次のよくあるエラーを扱います。

class MyAgent extends Agent {
	async onEmail(email) {
		try {
			await this.replyToEmail(email, {
				fromName: "Support Bot",
				body: "Thanks for your email!",
			});
		} catch (error) {
			switch (error.code) {
				case "E_SENDER_NOT_VERIFIED":
					console.error("Sender domain not verified. Verify in dashboard.");
					break;
				case "E_RATE_LIMIT_EXCEEDED":
					console.error("Rate limit exceeded. Back off and retry.");
					break;
				case "E_DAILY_LIMIT_EXCEEDED":
					console.error("Daily sending quota reached.");
					break;
				case "E_CONTENT_TOO_LARGE":
					console.error("Email content exceeds size limit.");
					break;
				default:
					console.error("Email sending failed:", error.message);
			}
		}
	}
}
class MyAgent extends Agent {
	async onEmail(email: AgentEmail) {
		try {
			await this.replyToEmail(email, {
				fromName: "Support Bot",
				body: "Thanks for your email!",
			});
		} catch (error) {
			switch (error.code) {
				case "E_SENDER_NOT_VERIFIED":
					console.error("Sender domain not verified. Verify in dashboard.");
					break;
				case "E_RATE_LIMIT_EXCEEDED":
					console.error("Rate limit exceeded. Back off and retry.");
					break;
				case "E_DAILY_LIMIT_EXCEEDED":
					console.error("Daily sending quota reached.");
					break;
				case "E_CONTENT_TOO_LARGE":
					console.error("Email content exceeds size limit.");
					break;
				default:
					console.error("Email sending failed:", error.message);
			}
		}
	}
}

よくあるエラーコード

エラーコード 説明 対処
E_SENDER_NOT_VERIFIED 送信元ドメイン / アドレスが未検証 Cloudflare ダッシュボードで検証する
E_RATE_LIMIT_EXCEEDED 送信レート制限に達した 指数バックオフを実装する
E_DAILY_LIMIT_EXCEEDED 日間クォータを超えた クォータリセットを待つか、プランを上げる
E_CONTENT_TOO_LARGE メールがサイズ上限を超えた 添付または本文を減らす
E_RECIPIENT_NOT_ALLOWED 受信者が許可リストにない 許可された宛先アドレスを確認する
E_RECIPIENT_SUPPRESSED 受信者が抑制リストにある 抑制リストから外す
E_VALIDATION_ERROR メール形式が不正 メールアドレスを確認する
E_TOO_MANY_RECIPIENTS 受信者が 50 件を超えている 複数回の送信に分割する

安全な返信ルーティング

エージェントがメールを送り、返信を期待するときは、安全な返信ルーティングを使います。攻撃者がヘッダーを偽造して任意のエージェントインスタンスへメールをルーティングするのを防ぎます。

仕組み

  1. 送信: replyToEmail() または sendEmail()secret 付きで呼ぶと、エージェントはルーティングヘッダー(X-Agent-NameX-Agent-ID)を HMAC-SHA256 で署名します。
  2. 受信: createSecureReplyEmailResolver は、ルーティング前に署名を検証します。
  3. 強制: メールが安全な resolver 経由でルーティングされた場合、replyToEmail() はシークレットを要求します(オプトアウトするには明示的に null)。

セットアップ

  1. 署名キーを Wrangler シークレットとして保存します。vars に入れたり、ソース管理にコミットしたりしないでください。

    npx wrangler secret put EMAIL_SECRET
  2. 組み合わせ resolver パターンを使います。

    export default {
    	async email(message, env) {
    		const secureReplyResolver = createSecureReplyEmailResolver(
    			env.EMAIL_SECRET,
    		);
    		const addressResolver = createAddressBasedEmailResolver("EmailAgent");
    
    		await routeAgentEmail(message, env, {
    			resolver: async (email, env) => {
    				const replyRouting = await secureReplyResolver(email, env);
    				if (replyRouting) return replyRouting;
    				return addressResolver(email, env);
    			},
    		});
    	},
    };
    export default {
     async email(message, env) {
      const secureReplyResolver = createSecureReplyEmailResolver(
       env.EMAIL_SECRET,
      );
      const addressResolver = createAddressBasedEmailResolver("EmailAgent");
    
      await routeAgentEmail(message, env, {
       resolver: async (email, env) => {
        const replyRouting = await secureReplyResolver(email, env);
        if (replyRouting) return replyRouting;
        return addressResolver(email, env);
       },
      });
     },
    } satisfies ExportedHandler<Env>;
  3. 送信メールに署名します。

    class MyAgent extends Agent {
    	async onEmail(email) {
    		await this.replyToEmail(email, {
    			fromName: "My Agent",
    			body: "Thanks for your email!",
    			secret: this.env.EMAIL_SECRET, // Signs the routing headers
    		});
    	}
    }
    class MyAgent extends Agent {
     async onEmail(email: AgentEmail) {
      await this.replyToEmail(email, {
       fromName: "My Agent",
       body: "Thanks for your email!",
       secret: this.env.EMAIL_SECRET, // Signs the routing headers
      });
     }
    }

強制の挙動

メールが createSecureReplyEmailResolver 経由でルーティングされた場合、replyToEmail() メソッドは署名を強制します。

secret の値 挙動
"my-secret" ヘッダーに署名する(安全)
undefined(省略) エラーを投げる — シークレットか明示的なオプトアウトが必要
null 許可されるが非推奨 — 署名を明示的にオプトアウトする

完全な例

送信メールを送り、安全な返信を扱う完全な Email Service エージェントの例です。

import { Agent, callable, routeAgentEmail } from "agents";
import {
	createAddressBasedEmailResolver,
	createSecureReplyEmailResolver,
} from "agents/email";
import PostalMime from "postal-mime";

export class EmailAgent extends Agent {
	@callable()
	async sendWelcome(to) {
		return this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: "[email protected]",
			subject: "Welcome!",
			text: "Thanks for signing up.",
			secret: this.env.EMAIL_SECRET,
		});
	}

	async onEmail(email) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		console.log(`Email from ${email.from}: ${parsed.subject}`);

		const emails = this.state.emails || [];
		emails.push({
			from: email.from,
			subject: parsed.subject,
			receivedAt: new Date().toISOString(),
		});
		this.setState({ ...this.state, emails });

		await this.replyToEmail(email, {
			fromName: "Support Bot",
			body: `Thanks for your email! We received: "${parsed.subject}"`,
			secret: this.env.EMAIL_SECRET,
		});
	}
}

export default {
	async email(message, env) {
		const secureReplyResolver = createSecureReplyEmailResolver(
			env.EMAIL_SECRET,
			{
				maxAge: 7 * 24 * 60 * 60, // 7 days
				onInvalidSignature: (email, reason) => {
					console.warn(`Invalid signature from ${email.from}: ${reason}`);
				},
			},
		);
		const addressResolver = createAddressBasedEmailResolver("EmailAgent");

		await routeAgentEmail(message, env, {
			resolver: async (email, env) => {
				const replyRouting = await secureReplyResolver(email, env);
				if (replyRouting) return replyRouting;
				return addressResolver(email, env);
			},
			onNoRoute: (email) => {
				console.warn(`No route found for email from ${email.from}`);
				email.setReject("Unknown recipient");
			},
		});
	},
};
import { Agent, callable, routeAgentEmail } from "agents";
import {
	createAddressBasedEmailResolver,
	createSecureReplyEmailResolver,
	type AgentEmail,
} from "agents/email";
import PostalMime from "postal-mime";

interface Env {
	EmailAgent: DurableObjectNamespace<EmailAgent>;
	EMAIL: SendEmail;
	EMAIL_SECRET: string;
}

export class EmailAgent extends Agent<Env> {
	@callable()
	async sendWelcome(to: string) {
		return this.sendEmail({
			binding: this.env.EMAIL,
			to,
			from: "[email protected]",
			subject: "Welcome!",
			text: "Thanks for signing up.",
			secret: this.env.EMAIL_SECRET,
		});
	}

	async onEmail(email: AgentEmail) {
		const raw = await email.getRaw();
		const parsed = await PostalMime.parse(raw);

		console.log(`Email from ${email.from}: ${parsed.subject}`);

		const emails = this.state.emails || [];
		emails.push({
			from: email.from,
			subject: parsed.subject,
			receivedAt: new Date().toISOString(),
		});
		this.setState({ ...this.state, emails });

		await this.replyToEmail(email, {
			fromName: "Support Bot",
			body: `Thanks for your email! We received: "${parsed.subject}"`,
			secret: this.env.EMAIL_SECRET,
		});
	}
}

export default {
	async email(message, env: Env) {
		const secureReplyResolver = createSecureReplyEmailResolver(
			env.EMAIL_SECRET,
			{
				maxAge: 7 * 24 * 60 * 60, // 7 days
				onInvalidSignature: (email, reason) => {
					console.warn(`Invalid signature from ${email.from}: ${reason}`);
				},
			},
		);
		const addressResolver = createAddressBasedEmailResolver("EmailAgent");

		await routeAgentEmail(message, env, {
			resolver: async (email, env) => {
				const replyRouting = await secureReplyResolver(email, env);
				if (replyRouting) return replyRouting;
				return addressResolver(email, env);
			},
			onNoRoute: (email) => {
				console.warn(`No route found for email from ${email.from}`);
				email.setReject("Unknown recipient");
			},
		});
	},
} satisfies ExportedHandler<Env>;

API リファレンス

EmailAddress

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

sendEmail

async sendEmail(options: {
	binding: EmailSendBinding;
	to: string | EmailAddress | (string | EmailAddress)[];
	from: string | EmailAddress;
	subject: string;
	text?: string;
	html?: string;
	replyTo?: string | EmailAddress;
	cc?: string | EmailAddress | (string | EmailAddress)[];
	bcc?: string | EmailAddress | (string | EmailAddress)[];
	inReplyTo?: string;
	headers?: Record<string, string>;
	secret?: string;
}): Promise<EmailSendResult>;

Email Service バインディング経由で送信メールを送ります。X-Agent-NameX-Agent-ID ヘッダーを自動で注入します。secret を渡すと、安全な返信ルーティングのためヘッダーを HMAC-SHA256 で署名します。

オプション 説明
binding send_email バインディング(例: this.env.EMAIL)。必須。
to 宛先アドレス、アドレス配列、または EmailAddress オブジェクト
from 送信元アドレスまたは EmailAddress オブジェクト
subject メールの件名
text プレーンテキスト本文(text / html のいずれかは必須)
html HTML 本文(text / html のいずれかは必須)
replyTo Reply-to アドレスまたは EmailAddress オブジェクト
cc CC 宛先アドレス、アドレス配列、または EmailAddress オブジェクト
bcc BCC 宛先アドレス、アドレス配列、または EmailAddress オブジェクト
inReplyTo スレッド用の Message-ID(In-Reply-To ヘッダーを設定)
headers 追加のカスタムヘッダー(衝突時はエージェントヘッダーが優先)
secret エージェントルーティングヘッダーの HMAC 署名用シークレット

routeAgentEmail

function routeAgentEmail<Env>(
	email: ForwardableEmailMessage,
	env: Env,
	options: {
		resolver: EmailResolver;
		onNoRoute?: (email: ForwardableEmailMessage) => void | Promise<void>;
	},
): Promise<void>;

resolver の判定に基づき、受信メールを適切な Agent へルーティングします。

オプション 説明
resolver メールをどのエージェントへルーティングするかを決める関数
onNoRoute ルーティング情報が見つからないときに呼ばれる任意のコールバック。メールの拒否や独自処理に使います。未指定の場合は警告を記録し、メールを破棄します。

createSecureReplyEmailResolver

function createSecureReplyEmailResolver(
	secret: string,
	options?: {
		maxAge?: number;
		onInvalidSignature?: (
			email: ForwardableEmailMessage,
			reason: SignatureFailureReason,
		) => void;
	},
): EmailResolver;

type SignatureFailureReason =
	| "missing_headers"
	| "expired"
	| "invalid"
	| "malformed_timestamp";

署名検証付きでメール返信をルーティングする resolver を作成します。

オプション 説明
secret HMAC 検証用のシークレットキー(署名に使ったキーと一致させる)
maxAge 署名の最大有効秒数(デフォルト: 30 日 / 2592000 秒)
onInvalidSignature 署名検証が失敗したときのログ用の任意コールバック

signAgentHeaders

function signAgentHeaders(
	secret: string,
	agentName: string,
	agentId: string,
): Promise<Record<string, string>>;

エージェントルーティングヘッダーを手動で署名します。X-Agent-NameX-Agent-IDX-Agent-SigX-Agent-Sig-Ts ヘッダーを含むオブジェクトを返します。

外部サービス経由でメールを送りつつ、安全な返信ルーティングを維持したいときに便利です。署名にはタイムスタンプが含まれ、デフォルトでは 30 日間有効です。

次のステップ

HTTP と SSE

Agent で HTTP リクエストを扱います。

Webhooks

外部サービスからのイベントを受け取ります。

Agents API

Agents SDK の完全な API リファレンスです。

役に立ちましたか?