Skip to content

Commit 09713cc

Browse files
committed
Allow to, cc or bcc for batch transactional emails
1 parent 9deffbf commit 09713cc

4 files changed

Lines changed: 69 additions & 10 deletions

File tree

src/tools/sendEmail/__tests__/batchSendTransactionalEmail.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,52 @@ describe("batchSendTransactionalEmail", () => {
130130
expect(result.content[0].text).toContain("'subject' is required");
131131
});
132132

133+
it("sends successfully when `to` is omitted but `cc`/`bcc` is provided", async () => {
134+
mockClient.batchSend.mockResolvedValue({
135+
success: true,
136+
responses: [{ success: true, message_ids: ["m-1"] }],
137+
});
138+
139+
await batchSendTransactionalEmail({
140+
base: { from: "sender@example.com", subject: "Hi", text: "Hello" },
141+
requests: [
142+
{
143+
cc: ["carol@example.com"],
144+
bcc: ["dan@example.com"],
145+
},
146+
],
147+
});
148+
149+
expect(mockClient.batchSend).toHaveBeenCalledWith({
150+
base: {
151+
from: { email: "sender@example.com" },
152+
subject: "Hi",
153+
text: "Hello",
154+
},
155+
requests: [
156+
{
157+
to: [],
158+
cc: [{ email: "carol@example.com" }],
159+
bcc: [{ email: "dan@example.com" }],
160+
},
161+
],
162+
});
163+
});
164+
165+
it("errors when none of `to`/`cc`/`bcc` has a recipient", async () => {
166+
const result = await batchSendTransactionalEmail({
167+
base: { from: "sender@example.com", subject: "Hi", text: "x" },
168+
requests: [{}],
169+
});
170+
171+
expect(result.isError).toBe(true);
172+
expect(result.content[0].text).toContain("requests[0]:");
173+
expect(result.content[0].text).toContain(
174+
"provide at least one recipient via 'to', 'cc', or 'bcc'"
175+
);
176+
expect(mockClient.batchSend).not.toHaveBeenCalled();
177+
});
178+
133179
it("surfaces API errors", async () => {
134180
mockClient.batchSend.mockRejectedValue(new Error("rate limited"));
135181

src/tools/sendEmail/batchSendTransactionalEmail.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,25 @@ async function batchSendTransactionalEmail({
109109
sdkBase.custom_variables = base.custom_variables;
110110
if (base?.headers !== undefined) sdkBase.headers = base.headers;
111111

112-
const sdkRequests = requests.map((req) => {
112+
const sdkRequests = requests.map((req, i) => {
113+
const toAddresses =
114+
req.to !== undefined ? normalizeToRecipients(req.to) : [];
115+
const ccAddresses =
116+
req.cc && req.cc.length > 0 ? normalizeAddressList(req.cc) : [];
117+
const bccAddresses =
118+
req.bcc && req.bcc.length > 0 ? normalizeAddressList(req.bcc) : [];
119+
120+
if (toAddresses.length + ccAddresses.length + bccAddresses.length === 0) {
121+
throw new Error(
122+
`requests[${i}]: provide at least one recipient via 'to', 'cc', or 'bcc'`
123+
);
124+
}
125+
113126
const r: Record<string, unknown> = {
114-
to: normalizeToRecipients(req.to),
127+
to: toAddresses,
115128
};
116-
if (req.cc && req.cc.length > 0) r.cc = normalizeAddressList(req.cc);
117-
if (req.bcc && req.bcc.length > 0) r.bcc = normalizeAddressList(req.bcc);
129+
if (ccAddresses.length > 0) r.cc = ccAddresses;
130+
if (bccAddresses.length > 0) r.bcc = bccAddresses;
118131
if (req.reply_to) r.reply_to = [toMailtrapAddress(req.reply_to)];
119132
if (req.subject !== undefined) r.subject = req.subject;
120133
if (req.text !== undefined) r.text = req.text;

src/tools/sendEmail/schemas/batchSendTransactionalEmail.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ const batchSendTransactionalEmailSchema = {
7878
type: "array",
7979
minItems: 1,
8080
description:
81-
"Per-recipient messages. Each entry must include `to`; other fields override `base`.",
81+
"Per-recipient messages. Each request must include at least one recipient via `to`, `cc`, or `bcc`; other fields override `base`.",
8282
items: {
8383
type: "object",
8484
properties: {
8585
to: {
8686
...addressArrayOrSingle,
8787
description:
88-
"Recipient(s): one address (string or `{ email, name? }`) or a non-empty array.",
88+
"Recipient(s): one address (string or `{ email, name? }`) or a non-empty array. Optional if `cc` or `bcc` is provided; at least one of `to`/`cc`/`bcc` must contain a recipient.",
8989
},
9090
cc: {
9191
type: "array",
@@ -103,7 +103,6 @@ const batchSendTransactionalEmailSchema = {
103103
},
104104
...inlineOrTemplateProps,
105105
},
106-
required: ["to"],
107106
additionalProperties: false,
108107
},
109108
},

src/types/mailtrap.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ export interface BatchSendEmailBase {
5858
}
5959

6060
/**
61-
* Per-recipient override in a batch send. `to` is required; any other field
62-
* overrides the corresponding `base` value for this single message.
61+
* Per-recipient override in a batch send. At least one of `to`/`cc`/`bcc`
62+
* must contain a recipient (validated at runtime); any other field overrides
63+
* the corresponding `base` value for this single message.
6364
*/
6465
export interface BatchSendEmailRequest {
65-
to: MailtrapAddressParam | MailtrapAddressParam[];
66+
to?: MailtrapAddressParam | MailtrapAddressParam[];
6667
cc?: MailtrapAddressParam[];
6768
bcc?: MailtrapAddressParam[];
6869
reply_to?: MailtrapAddressParam;

0 commit comments

Comments
 (0)