跳转到内容
搜索文档

Workers API

Process incoming emails with the email() handler in Cloudflare Workers. Forward, reply, reject, or process emails programmatically.

最后更新 查看 MarkdownAgent 设置

使用 Cloudflare Workers 中的 email() 处理程序处理入站邮件。这使你可以用自定义逻辑以编程方式处理邮件路由。

Email 处理程序语法

email 处理函数添加到 Worker 导出的 handlers 中:

export default {
	async email(message, env, ctx): Promise<void> {
		// Process incoming email
		await message.forward("[email protected]");
	},
} satisfies ExportedHandler<Env>;
from workers import WorkerEntrypoint

class Default(WorkerEntrypoint):
    async def email(self, message, env, ctx):
        await message.forward("[email protected]")

参数

参数 类型 说明
message ForwardableEmailMessage 入站邮件消息
env object Worker 环境绑定(KV、EMAIL 等)
ctx object 执行上下文,包含 waitUntil 函数

ForwardableEmailMessage 接口

message 参数提供对入站邮件的访问:

interface ForwardableEmailMessage {
	readonly from: string; // Sender email address (envelope MAIL FROM)
	readonly to: string; // Recipient email address (envelope RCPT TO)
	readonly headers: Headers; // Email headers (Subject, Message-ID, etc.)
	readonly raw: ReadableStream; // Raw MIME email content stream
	readonly rawSize: number; // Size of raw email in bytes
	readonly canBeForwarded: boolean; // Whether the message can be forwarded

	// Actions
	setReject(reason: string): void;
	forward(rcptTo: string, headers?: Headers): Promise<EmailSendResult>;
	reply(message: EmailMessage): Promise<EmailSendResult>;
}

属性

export default {
	async email(message, env, ctx): Promise<void> {
		// Access email metadata
		console.log(`From: ${message.from}`);
		console.log(`To: ${message.to}`);
		console.log(`Size: ${message.rawSize} bytes`);

		// Access headers
		const subject = message.headers.get("subject");
		const date = message.headers.get("date");
		const messageId = message.headers.get("message-id");

		console.log(`Subject: ${subject}`);
		console.log(`Date: ${date}`);
		console.log(`Message-ID: ${messageId}`);
	},
};

使用 postal-mime 解析入站邮件的 MIME 结构。解析器能正确处理 multipart 边界、传输编码和字符集。

import PostalMime from "postal-mime";

export default {
	async email(message, env, ctx): Promise<void> {
		const email = await PostalMime.parse(message.raw);

		console.log(`Subject: ${email.subject}`);
		console.log(`Text: ${email.text}`);
		console.log(`HTML: ${email.html}`);
	},
};

邮件操作

转发邮件

将入站邮件转发到已验证的目标地址:

export default {
	async email(message, env, ctx): Promise<void> {
		// Forward to a single address
		await message.forward("[email protected]");
	},
};
export default {
	async email(message, env, ctx): Promise<void> {
		const recipient = message.to;
		const subject = message.headers.get("subject") || "";

		// Route based on recipient
		if (recipient.includes("support@")) {
			await message.forward("[email protected]");
		} else if (recipient.includes("sales@")) {
			await message.forward("[email protected]");
		} else if (subject.toLowerCase().includes("urgent")) {
			await message.forward("[email protected]");
		} else {
			// Default routing
			await message.forward("[email protected]");
		}
	},
};
export default {
	async email(message, env, ctx): Promise<void> {
		const subject = message.headers.get("subject") || "";

		if (subject.toLowerCase().includes("security")) {
			// Forward to multiple addresses for security issues
			await Promise.all([
				message.forward("[email protected]"),
				message.forward("[email protected]"),
				message.forward("[email protected]"),
			]);
		} else {
			await message.forward("[email protected]");
		}
	},
};

使用自定义标头转发

转发时可添加自定义标头。通过 forward() 只能添加带 X- 前缀的标头,其他标头会被移除。

export default {
	async email(message, env, ctx): Promise<void> {
		// Create custom headers
		const customHeaders = new Headers();
		customHeaders.set("X-Processed-By", "Email-Worker");
		customHeaders.set("X-Processing-Time", new Date().toISOString());
		customHeaders.set("X-Original-Recipient", message.to);
		customHeaders.set("X-Spam-Score", "0.1"); // Example spam score

		// Forward with custom headers
		await message.forward("[email protected]", customHeaders);
	},
};

回复邮件

使用 message.reply() 发送自动回复。以此方式构建的回复会与原始消息形成会话线程,并经由同一 SMTP 会话传递,因此会保留原始 Message-ID 链。

通过 Workers API 发送回复必须满足以下要求,否则 reply() 会抛出异常:

  • 入站邮件必须具有有效的 DMARC 结果。
  • 每个 EmailMessage 事件中,一封邮件只能被回复一次。
  • 回复中的收件人必须与入站邮件的发件人一致。
  • 出站发件人域名必须与接收该邮件的域名一致。
  • 若入站邮件的 References 标头条目超过 100 个,回复会被拒绝,以防止回复循环和滥用。

回复载荷是由原始 MIME 字符串构建的 EmailMessage。下方示例使用 mimetext 构建 MIME 正文。mimetext 包需要 nodejs_compat 兼容性标志。

import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";

export default {
	async email(message, env, ctx): Promise<void> {
		const subject = message.headers.get("subject") || "";
		const messageId = message.headers.get("Message-ID");

		const reply = createMimeMessage();
		if (messageId) {
			reply.setHeader("In-Reply-To", messageId);
			reply.setHeader("References", messageId);
		}
		reply.setSender(message.to);
		reply.setRecipient(message.from);
		reply.setSubject(`Re: ${subject}`);
		reply.addMessage({
			contentType: "text/plain",
			data: "Thank you for your message. We have received your email and will respond shortly.",
		});
		reply.addMessage({
			contentType: "text/html",
			data: "<h1>Thank you for your message</h1><p>We have received your email and will respond shortly.</p>",
		});

		await message.reply(
			new EmailMessage(message.to, message.from, reply.asRaw()),
		);

		// Also forward to human team
		await message.forward("[email protected]");
	},
};
import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";

export default {
	async email(message, env, ctx): Promise<void> {
		const sender = message.from;
		const recipient = message.to;
		const subject = message.headers.get("subject") || "";
		const messageId = message.headers.get("Message-ID");

		// Don't reply to automated emails
		if (
			sender.includes("noreply") ||
			sender.includes("no-reply") ||
			subject.toLowerCase().includes("automated")
		) {
			await message.forward("[email protected]");
			return;
		}

		// Customized auto-reply based on recipient
		let html = "";

		if (recipient.includes("support@")) {
			html = `
                <h1>Support Request Received</h1>
                <p>Thank you for contacting support. Your request has been assigned ticket #${Date.now()}.</p>
                <p>Expected response time: 2-4 hours during business hours.</p>
            `;
		} else if (recipient.includes("sales@")) {
			html = `
                <h1>Sales Inquiry Received</h1>
                <p>Thank you for your interest in our products.</p>
                <p>A sales representative will contact you within 24 hours.</p>
            `;
		} else {
			html = `
                <h1>Message Received</h1>
                <p>Thank you for your message. We will respond within 2 business days.</p>
            `;
		}

		const reply = createMimeMessage();
		if (messageId) {
			reply.setHeader("In-Reply-To", messageId);
			reply.setHeader("References", messageId);
		}
		reply.setSender(recipient);
		reply.setRecipient(sender);
		reply.setSubject(`Re: ${subject}`);
		reply.addMessage({
			contentType: "text/plain",
			data: html.replace(/<[^>]*>/g, ""),
		});
		reply.addMessage({ contentType: "text/html", data: html });

		await message.reply(new EmailMessage(recipient, sender, reply.asRaw()));

		// Forward to appropriate team
		await message.forward("[email protected]");
	},
};

拒绝邮件

以永久 SMTP 错误拒绝邮件:

export default {
	async email(message, env, ctx): Promise<void> {
		const sender = message.from;

		// Block specific senders
		const blockedDomains = ["spam.com", "unwanted.net"];
		const senderDomain = sender.split("@")[1];

		if (blockedDomains.includes(senderDomain)) {
			message.setReject("Sender domain not allowed");
			return;
		}

		// Continue processing
		await message.forward("[email protected]");
	},
};
export default {
	async email(message, env, ctx): Promise<void> {
		const subject = message.headers.get("subject") || "";

		// Reject based on subject content
		const spamKeywords = ["buy now", "limited time", "act fast", "urgent"];
		const containsSpam = spamKeywords.some((keyword) =>
			subject.toLowerCase().includes(keyword),
		);

		if (containsSpam) {
			message.setReject("Message appears to be spam");
			return;
		}

		// Check message size
		if (message.rawSize > 25 * 1024 * 1024) {
			// 25 MiB limit (inbound message size)
			message.setReject("Message too large");
			return;
		}

		// Continue processing
		await message.forward("[email protected]");
	},
};

错误处理

在邮件处理中妥善处理错误:

export default {
	async email(message, env, ctx): Promise<void> {
		try {
			// Main email processing logic
			await processEmail(message, env);
		} catch (error) {
			console.error("Email processing failed:", error);

			// Log error for monitoring
			if (env.ERROR_LOGS) {
				await env.ERROR_LOGS.put(
					`error-${Date.now()}`,
					JSON.stringify({
						error: error.message,
						stack: error.stack,
						from: message.from,
						to: message.to,
						timestamp: new Date().toISOString(),
					}),
				);
			}

			// Fallback: forward to admin
			try {
				await message.forward("[email protected]");
			} catch (fallbackError) {
				console.error("Fallback forwarding failed:", fallbackError);
				// Last resort: reject the email
				message.setReject("Internal processing error");
			}
		}
	},
};

async function processEmail(message, env) {
	// Your main email processing logic here
	const recipient = message.to;

	if (recipient.includes("support@")) {
		await message.forward("[email protected]");
	} else if (recipient.includes("sales@")) {
		await message.forward("[email protected]");
	} else {
		await message.forward("[email protected]");
	}
}

后续步骤

这篇文档对您有帮助吗?