Skip to content

Commit f729c29

Browse files
committed
Support SendEmail bindings
1 parent afc00f6 commit f729c29

File tree

7 files changed

+56
-20
lines changed

7 files changed

+56
-20
lines changed

packages/miniflare/src/plugins/email/index.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import { Service, Worker_Binding } from "../../runtime";
55
import {
66
getUserBindingServiceName,
77
Plugin,
8+
remoteProxyClientWorker,
9+
RemoteProxyConnectionString,
810
WORKER_BINDING_SERVICE_LOOPBACK,
911
} from "../shared";
1012

1113
// Define the mutually exclusive schema
1214
const EmailBindingOptionsSchema = z
1315
.object({
1416
name: z.string(),
17+
remoteProxyConnectionString: z
18+
.custom<RemoteProxyConnectionString>()
19+
.optional(),
1520
})
1621
.and(
1722
z.union([
@@ -53,10 +58,12 @@ export const EMAIL_PLUGIN: Plugin<typeof EmailOptionsSchema> = {
5358

5459
const sendEmailBindings = options.email.send_email;
5560

56-
return sendEmailBindings.map(({ name }) => ({
61+
return sendEmailBindings.map(({ name, remoteProxyConnectionString }) => ({
5762
name,
5863
service: {
59-
entrypoint: "SendEmailBinding",
64+
entrypoint: remoteProxyConnectionString
65+
? undefined
66+
: "SendEmailBinding",
6067
name: getUserBindingServiceName(SERVICE_SEND_EMAIL_WORKER_PREFIX, name),
6168
},
6269
}));
@@ -67,22 +74,25 @@ export const EMAIL_PLUGIN: Plugin<typeof EmailOptionsSchema> = {
6774
async getServices(args) {
6875
const services: Service[] = [];
6976

70-
for (const { name, ...config } of args.options.email?.send_email ?? []) {
77+
for (const { name, remoteProxyConnectionString, ...config } of args.options
78+
.email?.send_email ?? []) {
7179
services.push({
7280
name: getUserBindingServiceName(SERVICE_SEND_EMAIL_WORKER_PREFIX, name),
73-
worker: {
74-
compatibilityDate: "2025-03-17",
75-
modules: [
76-
{
77-
name: "send_email.mjs",
78-
esModule: SEND_EMAIL_BINDING(),
81+
worker: remoteProxyConnectionString
82+
? remoteProxyClientWorker(remoteProxyConnectionString, name)
83+
: {
84+
compatibilityDate: "2025-03-17",
85+
modules: [
86+
{
87+
name: "send_email.mjs",
88+
esModule: SEND_EMAIL_BINDING(),
89+
},
90+
],
91+
bindings: [
92+
...buildJsonBindings(config),
93+
WORKER_BINDING_SERVICE_LOOPBACK,
94+
],
7995
},
80-
],
81-
bindings: [
82-
...buildJsonBindings(config),
83-
WORKER_BINDING_SERVICE_LOOPBACK,
84-
],
85-
},
8696
});
8797
}
8898

packages/miniflare/src/workers/shared/remote-proxy-client.worker.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ export default class Client extends WorkerEntrypoint<Env> {
4141
return Reflect.get(target, prop);
4242
}
4343

44-
const value = Reflect.get(stub, prop);
45-
return value;
44+
return Reflect.get(stub, prop);
4645
},
4746
});
4847
}

packages/wrangler/src/config/validation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2754,6 +2754,7 @@ const validateSendEmailBinding: ValidatorFn = (diagnostics, field, value) => {
27542754
"destination_address",
27552755
"name",
27562756
"binding",
2757+
"experimental_remote",
27572758
]);
27582759

27592760
return isValid;

packages/wrangler/src/deployment-bundle/worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface CfKvNamespace {
9191
*/
9292
export type CfSendEmailBindings = {
9393
name: string;
94+
experimental_remote?: boolean;
9495
} & (
9596
| { destination_address?: string }
9697
| { allowed_destination_addresses?: string[] }

packages/wrangler/src/dev/miniflare.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,13 @@ export function buildMiniflareBindingOptions(
973973
]) ?? []
974974
),
975975
email: {
976-
send_email: bindings.send_email,
976+
send_email: bindings.send_email?.map((b) => ({
977+
...b,
978+
remoteProxyConnectionString:
979+
b.experimental_remote && remoteProxyConnectionString
980+
? remoteProxyConnectionString
981+
: undefined,
982+
})),
977983
},
978984
images:
979985
bindings.images && (config.imagesLocalMode || remoteBindingsEnabled)

packages/wrangler/src/utils/print-bindings.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,9 @@ export function printBindings(
213213
destination_address ||
214214
allowed_destination_addresses?.join(", ") ||
215215
"unrestricted",
216-
mode: getMode({ isSimulatedLocally: true }),
216+
mode: getMode({
217+
isSimulatedLocally: !emailBinding.experimental_remote,
218+
}),
217219
};
218220
})
219221
);

packages/wrangler/templates/remoteBindings/proxyServerWorker/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
import { receiveRpcOverHttp } from "@cloudflare/jsrpc";
2+
import { EmailMessage } from "cloudflare:email";
23

4+
// For most bindings, we expose them as RPC stubs directly to @cloudflare/jsrpc.
5+
// However, SendEmail bindings need to take EmailMessage as their first parameter, which is not serialisable
6+
// As such, we reconstruct it before sending it on to the binding.
7+
// See also packages/miniflare/src/workers/email/email.worker.ts
8+
function getExposedBinding(b: any) {
9+
if (b.constructor.name === "SendEmail") {
10+
return {
11+
async send(e: ForwardableEmailMessage) {
12+
// @ts-expect-error EmailMessage::raw is defined in packages/miniflare/src/workers/email/email.worker.ts
13+
const message = new EmailMessage(e.from, e.to, e["EmailMessage::raw"]);
14+
return b.send(message);
15+
},
16+
};
17+
}
18+
return b;
19+
}
320
export default {
421
async fetch(request, env) {
522
if (request.headers.get("Upgrade")) {
623
const url = new URL(request.url);
724
return receiveRpcOverHttp(
825
request,
9-
env[url.searchParams.get("MF-Binding")!]
26+
getExposedBinding(env[url.searchParams.get("MF-Binding")!])
1027
);
1128
}
1229
const targetBinding = request.headers.get("MF-Binding");

0 commit comments

Comments
 (0)