INTEGRITY Документация

API Workers

Обрабатывайте входящие письма с помощью email() обработчик в Cloudflare Workers. Это позволяет программно управлять маршрутизацией писем с помощью собственной логики.

Синтаксис обработчика писем

Добавьте email функцию обработчика в экспортируемые обработчики вашего Worker:

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]");
		}
	},
};

Пересылка с пользовательскими заголовками

Добавляйте собственные заголовки при пересылке. Учитываются только заголовки с X- префикс можно добавить через forward(). Остальные заголовки удаляются.

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() выбрасывает исключение:

Тело ответа представляет собой EmailMessage создано из необработанной MIME-строки. В примерах ниже используется 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]");
	}
}

Следующие шаги