1// Async (non-Effect) Worker that exercises the inbound `email()` handler
2// against the local runtime's `/cdn-cgi/handler/email` trigger route.
3// Referenced by path only (never value-imported by the test) so the
4// top-level `cloudflare:email` import is evaluated exclusively inside
5// workerd.
6//
7// Every `email()` invocation is recorded into a KV namespace (read back by
8// the test via `GET /received`). The message subject drives extra behavior
9// so one worker covers the accept/reject/reply cases:
10// - `reject-me` → `setReject(...)` (the trigger route answers 400)
11// - `reply-me:<marker>` → `message.reply(new EmailMessage(...))` whose body
12// carries `reply-marker:<marker>` for the test to grep in the persisted
13// `.eml`.
14import { EmailMessage } from "cloudflare:email";
15
16interface KVLike {
17 get(key: string): Promise<string | null>;
18 put(key: string, value: string): Promise<void>;
19}
20
21type Env = { KV: KVLike };
22
23interface EmailMessageLike {
24 from: string;
25 to: string;
26 raw: ReadableStream<Uint8Array>;
27 rawSize: number;
28 headers: Headers;
29 setReject(reason: string): void;
30 reply(message: unknown): Promise<{ messageId: string }>;
31}
32
33export default {
34 fetch: async (request: Request, env: Env) => {
35 const url = new URL(request.url);
36 if (url.pathname === "/received") {
37 const raw = await env.KV.get("received");
38 return Response.json(raw ? JSON.parse(raw) : []);
39 }
40 return new Response("ok");
41 },
42 email: async (message: EmailMessageLike, env: Env) => {
43 const subject = message.headers.get("subject") ?? "";
44 const record = {
45 from: message.from,
46 to: message.to,
47 rawSize: message.rawSize,
48 raw: await new Response(message.raw).text(),
49 subject,
50 messageIdHeader: message.headers.get("message-id"),
51 };
52 const prior = await env.KV.get("received");
53 const received: unknown[] = prior ? JSON.parse(prior) : [];
54 received.push(record);
55 await env.KV.put("received", JSON.stringify(received));
56
57 if (subject === "reject-me") {
58 await message.setReject("I don't like this email");
59 return;
60 }
61 if (subject.startsWith("reply-me:")) {
62 const marker = subject.slice("reply-me:".length);
63 // The reply must be a valid RFC 5322 message whose `From:` matches the
64 // reply envelope sender (the incoming envelope recipient), with an
65 // `In-Reply-To` matching the incoming Message-ID and its own
66 // Message-ID — the runtime validates all three before persisting.
67 const replyRaw = [
68 `From: recipient <${message.to}>`,
69 `To: sender <${message.from}>`,
70 `In-Reply-To: ${message.headers.get("message-id")}`,
71 `Message-ID: <reply-${marker}@example.com>`,
72 "MIME-Version: 1.0",
73 "Content-Type: text/plain",
74 "",
75 `reply-marker:${marker}`,
76 ].join("\n");
77 await message.reply(new EmailMessage(message.to, message.from, replyRaw));
78 }
79 },
80};