1/**
2 * Inbound email worker: drive it locally by POSTing a raw MIME message to
3 * `/cdn-cgi/handler/email?from=...&to=...`. The `email()` handler records
4 * every message into the INBOX KV namespace (read back via
5 * `GET /email/received`); a subject starting with `reject-me` is rejected
6 * via `setReject`, which the sender observes as a 400.
7 *
8 * NOTE: this handler lives on its own worker (not MediaWorker) because in
9 * local dev the images/stream simulators insert a fetch-only middleware
10 * between the dev entry and the user worker, which currently swallows the
11 * entry's RPC dispatch of non-fetch events (`email()`, `queue()`,
12 * `scheduled()`). Keep inbound email handlers on workers without images or
13 * stream bindings until that is fixed in the local runtime.
14 */
15interface InboxKV {
16 get(key: string): Promise<string | null>;
17 put(key: string, value: string): Promise<void>;
18}
19
20interface Env {
21 INBOX: InboxKV;
22}
23
24interface InboundEmailLike {
25 from: string;
26 to: string;
27 raw: ReadableStream<Uint8Array>;
28 rawSize: number;
29 headers: Headers;
30 // `void` in production; locally the message crosses JSRPC, so the call
31 // returns a promise that must be awaited for the rejection to register.
32 setReject(reason: string): void | Promise<void>;
33 reply(message: unknown): Promise<{ messageId: string }>;
34}
35
36export default {
37 async fetch(request: Request, env: Env): Promise<Response> {
38 const url = new URL(request.url);
39 if (url.pathname === "/email/received") {
40 const raw = await env.INBOX.get("received");
41 return Response.json(raw ? JSON.parse(raw) : []);
42 }
43 return new Response("ok");
44 },
45
46 async email(message: InboundEmailLike, env: Env): Promise<void> {
47 const subject = message.headers.get("subject") ?? "";
48 const record = {
49 from: message.from,
50 to: message.to,
51 rawSize: message.rawSize,
52 subject,
53 messageIdHeader: message.headers.get("message-id"),
54 };
55 const prior = await env.INBOX.get("received");
56 const received: unknown[] = prior ? JSON.parse(prior) : [];
57 received.push(record);
58 await env.INBOX.put("received", JSON.stringify(received));
59
60 if (subject.startsWith("reject-me")) {
61 await message.setReject("I don't like this email");
62 }
63 },
64};