Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,62 @@ describe("SendGridTransport", () => {
});
});

test("send: forwards customArgs object", async () => {
const transport = new SendGridTransport({ apiKey: "KEY" });
const fakeMail: any = {
normalize: (cb: any) =>
cb(null, {
from: { address: "a@x.com" },
to: [{ address: "b@x.com" }],
subject: "Custom args test",
customArgs: { campaign: "welcome", source: "signup" },
}),
};
const cb = jest.fn();

transport.send(fakeMail, cb);
await new Promise((r) => setImmediate(r));

const sent = (sgMail.send as jest.Mock).mock.calls[0][0];
expect(sent.customArgs).toEqual({ campaign: "welcome", source: "signup" });
expect(cb).toHaveBeenCalledWith(null, {
messageId: null,
queueId: "test-message-id",
accepted: ["b@x.com"],
rejected: [],
pending: [],
response: "status=202",
});
});

test("send: forwards ipPoolName string", async () => {
const transport = new SendGridTransport({ apiKey: "KEY" });
const fakeMail: any = {
normalize: (cb: any) =>
cb(null, {
from: { address: "a@x.com" },
to: [{ address: "b@x.com" }],
subject: "IP pool test",
ipPoolName: "transactional",
}),
};
const cb = jest.fn();

transport.send(fakeMail, cb);
await new Promise((r) => setImmediate(r));

const sent = (sgMail.send as jest.Mock).mock.calls[0][0];
expect(sent.ipPoolName).toEqual("transactional");
expect(cb).toHaveBeenCalledWith(null, {
messageId: null,
queueId: "test-message-id",
accepted: ["b@x.com"],
rejected: [],
pending: [],
response: "status=202",
});
});

test("send: deduplicates and normalizes email addresses", async () => {
const transport = new SendGridTransport();
const source = {
Expand Down
42 changes: 42 additions & 0 deletions firestore-send-email/functions/__tests__/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,28 @@ describe("validatePayload", () => {
});
});

it("should validate a SendGrid payload with customArgs", () => {
const validPayload = {
to: "test@example.com",
sendGrid: {
templateId: "d-template-id",
customArgs: { campaign: "welcome", source: "signup" },
},
};
expect(() => validatePayload(validPayload)).not.toThrow();
});

it("should validate a SendGrid payload with ipPoolName", () => {
const validPayload = {
to: "test@example.com",
sendGrid: {
templateId: "d-template-id",
ipPoolName: "transactional",
},
};
expect(() => validatePayload(validPayload)).not.toThrow();
});

it("should validate a SendGrid payload with only mailSettings", () => {
const validPayload = {
to: "test@example.com",
Expand Down Expand Up @@ -255,6 +277,26 @@ describe("validatePayload", () => {
);
});

it("should throw ValidationError for SendGrid customArgs with non-string values", () => {
const invalidPayload = {
to: "test@example.com",
sendGrid: {
customArgs: { campaign: 123 },
},
};
expect(() => validatePayload(invalidPayload)).toThrow(ValidationError);
});

it("should throw ValidationError for SendGrid ipPoolName with non-string value", () => {
const invalidPayload = {
to: "test@example.com",
sendGrid: {
ipPoolName: 123,
},
};
expect(() => validatePayload(invalidPayload)).toThrow(ValidationError);
});

it("should throw ValidationError for custom template without name", () => {
const invalidPayload = {
to: "test@example.com",
Expand Down
2 changes: 2 additions & 0 deletions firestore-send-email/functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ async function deliver(ref: DocumentReference): Promise<void> {
templateId: payload.sendGrid?.templateId,
dynamicTemplateData: payload.sendGrid?.dynamicTemplateData,
mailSettings: payload.sendGrid?.mailSettings,
customArgs: payload.sendGrid?.customArgs,
ipPoolName: payload.sendGrid?.ipPoolName,
};

logs.info("Sending via transport.sendMail()", { mailOptions });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ export class SendGridTransport {
case "mailSettings":
msg.mailSettings = source.mailSettings;
break;
case "customArgs":
msg.customArgs = source.customArgs;
break;
case "ipPoolName":
msg.ipPoolName = source.ipPoolName;
break;

default:
msg[key] = source[key];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface MailSource {
templateId?: string;
dynamicTemplateData?: Record<string, unknown>;
mailSettings?: Record<string, unknown>;
customArgs?: Record<string, string>;
ipPoolName?: string;

[key: string]: unknown;
}
Expand Down Expand Up @@ -91,5 +93,7 @@ export interface SendGridMessage {
templateId?: string;
dynamicTemplateData?: Record<string, unknown>;
mailSettings?: Record<string, unknown>;
customArgs?: Record<string, string>;
ipPoolName?: string;
[key: string]: unknown;
}
4 changes: 4 additions & 0 deletions firestore-send-email/functions/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export interface QueuePayload {
templateId?: string;
dynamicTemplateData?: { [key: string]: any };
mailSettings?: { [key: string]: any };
customArgs?: Record<string, string>;
ipPoolName?: string;
};
to: string[];
toUids?: string[];
Expand Down Expand Up @@ -127,4 +129,6 @@ export interface ExtendedSendMailOptions extends nodemailer.SendMailOptions {
templateId?: string;
dynamicTemplateData?: Record<string, any>;
mailSettings?: Record<string, any>;
customArgs?: Record<string, string>;
ipPoolName?: string;
}
2 changes: 2 additions & 0 deletions firestore-send-email/functions/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ const sendGridSchema = z
templateId: z.string().optional(),
dynamicTemplateData: z.record(z.any()).optional(),
mailSettings: z.record(z.any()).optional(),
customArgs: z.record(z.string()).optional(),
ipPoolName: z.string().optional(),
})
.refine(
(data) => {
Expand Down
Loading