Skip to content

Commit 5c531cd

Browse files
ttoinoOpenCodeclaude
committed
feat(wrangler): add email.sending event subscription source
Add `email.sending` as an event subscription source for queues. `wrangler queues subscription create` now accepts `--source email.sending` with the required `--zone-id` and `--domain` flags, and `wrangler queues subscription get` renders the sending domain as the subscription resource. Co-authored-by: OpenCode <noreply@opencode.ai> Co-authored-by: claude-opus-4-8 <noreply@anthropic.com>
1 parent 3f3afbb commit 5c531cd

5 files changed

Lines changed: 183 additions & 3 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
Add `email.sending` as an event subscription source for queues
6+
7+
`wrangler queues subscription create` now accepts `--source email.sending` alongside two new flags, `--zone-id` and `--domain`, which identify the zone and the sending domain (zone apex or a verified subdomain) to subscribe to. Both flags are required for this source. The subscription's resource is displayed as the sending domain in `wrangler queues subscription get`.

packages/wrangler/src/__tests__/queues/queues-subscription.test.ts

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,24 @@ describe("queues subscription", () => {
6161
events: ["namespace.created"],
6262
};
6363

64+
const mockSubscriptionEmail: EventSubscription = {
65+
id: "sub-email",
66+
created_at: "2024-01-01T00:00:00Z",
67+
modified_at: "2024-01-01T00:00:00Z",
68+
name: "Test Subscription Email",
69+
enabled: true,
70+
source: {
71+
type: EventSourceType.EMAIL_SENDING,
72+
zone_id: "zone-123",
73+
domain: "example.com",
74+
},
75+
destination: {
76+
type: "queues.queue",
77+
queue_id: expectedQueueId,
78+
},
79+
events: ["message.delivered"],
80+
};
81+
6482
describe("create", () => {
6583
it("should show the correct help text", async ({ expect }) => {
6684
await runWrangler("queues subscription create --help");
@@ -84,13 +102,15 @@ describe("queues subscription", () => {
84102
-v, --version Show version number [boolean]
85103
86104
OPTIONS
87-
--source The event source type [string] [required] [choices: "images", "kv", "r2", "superSlurper", "vectorize", "workersAi.model", "workersBuilds.worker", "workflows.workflow"]
105+
--source The event source type [string] [required] [choices: "email.sending", "images", "kv", "r2", "superSlurper", "vectorize", "workersAi.model", "workersBuilds.worker", "workflows.workflow"]
88106
--events Comma-separated list of event types to subscribe to [string] [required]
89107
--name Name for the subscription (auto-generated if not provided) [string]
90108
--enabled Whether the subscription should be active [boolean] [default: true]
91109
--model-name Workers AI model name (required for workersAi.model source) [string]
92110
--worker-name Worker name (required for workersBuilds.worker source) [string]
93-
--workflow-name Workflow name (required for workflows.workflow source) [string]"
111+
--workflow-name Workflow name (required for workflows.workflow source) [string]
112+
--zone-id Zone ID (required for email.sending source) [string]
113+
--domain Sending domain — zone apex or verified subdomain (required for email.sending source) [string]"
94114
`);
95115
});
96116

@@ -187,6 +207,79 @@ describe("queues subscription", () => {
187207
`);
188208
});
189209

210+
it("should create a subscription for email.sending source", async ({
211+
expect,
212+
}) => {
213+
const queueNameResolveRequest = mockGetQueueByNameRequest(
214+
expectedQueueName,
215+
{
216+
queue_id: expectedQueueId,
217+
queue_name: expectedQueueName,
218+
created_on: "",
219+
producers: [],
220+
consumers: [],
221+
producers_total_count: 0,
222+
consumers_total_count: 0,
223+
modified_on: "",
224+
}
225+
);
226+
227+
const expectedRequest: Partial<CreateEventSubscriptionRequest> = {
228+
name: "testQueue email.sending",
229+
enabled: true,
230+
source: {
231+
type: EventSourceType.EMAIL_SENDING,
232+
zone_id: "zone-123",
233+
domain: "example.com",
234+
},
235+
events: ["message.delivered"],
236+
};
237+
238+
const createRequest = mockCreateSubscriptionRequest(
239+
expectedRequest,
240+
expectedQueueId
241+
);
242+
243+
await runWrangler(
244+
"queues subscription create testQueue --source email.sending --events message.delivered --zone-id zone-123 --domain example.com"
245+
);
246+
247+
expect(queueNameResolveRequest.count).toEqual(1);
248+
expect(createRequest.count).toEqual(1);
249+
expect(std.err).toMatchInlineSnapshot(`""`);
250+
expect(std.out).toMatchInlineSnapshot(`
251+
"
252+
⛅️ wrangler x.x.x
253+
──────────────────
254+
Creating event subscription for queue 'testQueue'...
255+
✨ Successfully created event subscription 'testQueue email.sending' with id 'sub-123'."
256+
`);
257+
});
258+
259+
it("should show error when zone-id is missing for email.sending source", async ({
260+
expect,
261+
}) => {
262+
await expect(
263+
runWrangler(
264+
"queues subscription create testQueue --source email.sending --events message.delivered --domain example.com"
265+
)
266+
).rejects.toThrowErrorMatchingInlineSnapshot(
267+
`[Error: --zone-id is required when using source 'email.sending']`
268+
);
269+
});
270+
271+
it("should show error when domain is missing for email.sending source", async ({
272+
expect,
273+
}) => {
274+
await expect(
275+
runWrangler(
276+
"queues subscription create testQueue --source email.sending --events message.delivered --zone-id zone-123"
277+
)
278+
).rejects.toThrowErrorMatchingInlineSnapshot(
279+
`[Error: --domain is required when using source 'email.sending']`
280+
);
281+
});
282+
190283
it("should create subscription with custom name and disabled state", async ({
191284
expect,
192285
}) => {
@@ -254,7 +347,7 @@ describe("queues subscription", () => {
254347
)
255348
).rejects.toThrowErrorMatchingInlineSnapshot(`
256349
[Error: Invalid values:
257-
Argument: source, Given: "invalid", Choices: "images", "kv", "r2", "superSlurper", "vectorize", "workersAi.model", "workersBuilds.worker", "workflows.workflow"]
350+
Argument: source, Given: "invalid", Choices: "email.sending", "images", "kv", "r2", "superSlurper", "vectorize", "workersAi.model", "workersBuilds.worker", "workflows.workflow"]
258351
`);
259352
});
260353

@@ -528,6 +621,44 @@ describe("queues subscription", () => {
528621
`);
529622
});
530623

624+
it("should render the sending domain as the resource for email.sending source", async ({
625+
expect,
626+
}) => {
627+
mockGetQueueByNameRequest("testQueue", {
628+
queue_id: expectedQueueId,
629+
queue_name: "testQueue",
630+
created_on: "",
631+
modified_on: "",
632+
producers: [],
633+
consumers: [],
634+
producers_total_count: 0,
635+
consumers_total_count: 0,
636+
});
637+
const getRequest = mockGetSubscriptionRequest(
638+
"sub-email",
639+
mockSubscriptionEmail
640+
);
641+
642+
await runWrangler("queues subscription get testQueue --id sub-email");
643+
644+
expect(getRequest.count).toEqual(1);
645+
expect(std.err).toMatchInlineSnapshot(`""`);
646+
expect(std.out).toMatchInlineSnapshot(`
647+
"
648+
⛅️ wrangler x.x.x
649+
──────────────────
650+
ID: sub-email
651+
Name: Test Subscription Email
652+
Source: email.sending
653+
Resource: example.com
654+
Queue ID: queueId
655+
Events: message.delivered
656+
Enabled: Yes
657+
Created At: 1/1/2024, 12:00:00 AM
658+
Modified At: 1/1/2024, 12:00:00 AM"
659+
`);
660+
});
661+
531662
it('supports valid json output with "--json" flag', async ({ expect }) => {
532663
mockGetQueueByNameRequest("testQueue", {
533664
queue_id: expectedQueueId,

packages/wrangler/src/queues/cli/commands/subscription/create.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,30 @@ function parseSourceArgument(
1717
modelName?: string;
1818
workerName?: string;
1919
workflowName?: string;
20+
zoneId?: string;
21+
domain?: string;
2022
}
2123
): EventSource {
2224
switch (source as EventSourceType) {
25+
case EventSourceType.EMAIL_SENDING:
26+
if (!args.zoneId) {
27+
throw new UserError(
28+
`--zone-id is required when using source '${EventSourceType.EMAIL_SENDING}'`,
29+
{ telemetryMessage: "queues subscription create missing zone id" }
30+
);
31+
}
32+
if (!args.domain) {
33+
throw new UserError(
34+
`--domain is required when using source '${EventSourceType.EMAIL_SENDING}'`,
35+
{ telemetryMessage: "queues subscription create missing domain" }
36+
);
37+
}
38+
return {
39+
type: EventSourceType.EMAIL_SENDING,
40+
zone_id: args.zoneId,
41+
domain: args.domain,
42+
};
43+
2344
case EventSourceType.IMAGES:
2445
return { type: EventSourceType.IMAGES };
2546

@@ -128,12 +149,23 @@ export const queuesSubscriptionCreateCommand = createCommand({
128149
describe: "Workflow name (required for workflows.workflow source)",
129150
type: "string",
130151
},
152+
"zone-id": {
153+
describe: "Zone ID (required for email.sending source)",
154+
type: "string",
155+
},
156+
domain: {
157+
describe:
158+
"Sending domain — zone apex or verified subdomain (required for email.sending source)",
159+
type: "string",
160+
},
131161
},
132162
async handler(args, { config }) {
133163
const source = parseSourceArgument(args.source, {
134164
modelName: args.modelName,
135165
workerName: args.workerName,
136166
workflowName: args.workflowName,
167+
zoneId: args.zoneId,
168+
domain: args.domain,
137169
});
138170

139171
const events = args.events

packages/wrangler/src/queues/cli/commands/subscription/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export function getSourceType(source: EventSource): string {
77

88
export function getSourceResource(source: EventSource): string {
99
switch (source.type) {
10+
case EventSourceType.EMAIL_SENDING:
11+
return source.domain;
1012
case EventSourceType.WORKERS_AI_MODEL:
1113
return source.model_name;
1214
case EventSourceType.WORKERS_BUILDS_WORKER:

packages/wrangler/src/queues/subscription-types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface EventDestination {
1515
}
1616

1717
export enum EventSourceType {
18+
EMAIL_SENDING = "email.sending",
1819
IMAGES = "images",
1920
KV = "kv",
2021
R2 = "r2",
@@ -28,6 +29,7 @@ export enum EventSourceType {
2829
export const EVENT_SOURCE_TYPES = Object.values(EventSourceType);
2930

3031
export type EventSource =
32+
| EmailSendingEventSource
3133
| ImagesEventSource
3234
| KvEventSource
3335
| R2EventSource
@@ -37,6 +39,12 @@ export type EventSource =
3739
| WorkersBuildsWorkerEventSource
3840
| WorkflowsWorkflowEventSource;
3941

42+
export interface EmailSendingEventSource {
43+
type: EventSourceType.EMAIL_SENDING;
44+
zone_id: string;
45+
domain: string;
46+
}
47+
4048
export interface ImagesEventSource {
4149
type: EventSourceType.IMAGES;
4250
}

0 commit comments

Comments
 (0)