* fix: join multiple Reply-To addresses as comma-separated string Some SMTP providers (e.g., SendLayer) reject emails when Reply-To is passed as an array to nodemailer, which serializes it as multiple Reply-To headers. Using a comma-joined string is RFC 2822 compliant and works universally across all SMTP providers. Fixes #28610 * test: add unit tests for getReplyToHeader SMTP compatibility - Verify replyTo is always returned as comma-separated string, not array - Test single email, multiple emails, and empty email cases - Add RFC 5322 compliance test for SMTP compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add RFC 5322 reference comment to getReplyToHeader tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Lawrence Christian <67164412+LCNDevs@users.noreply.github.com> Co-authored-by: Romit <85230081+romitg2@users.noreply.github.com> Co-authored-by: Romit <romitgabani1.work@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { getReplyToHeader } from "./getReplyToHeader";
|
|
|
|
/**
|
|
* RFC 5322 (Internet Message Format) specifies that the Reply-To header must be
|
|
* a comma-separated list of addresses, not an array. Many SMTP servers reject arrays.
|
|
* Spec: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2
|
|
*/
|
|
|
|
vi.mock("./getReplyToEmail", () => ({
|
|
getReplyToEmail: vi.fn((calEvent, excludeOrganizerEmail) => {
|
|
if (excludeOrganizerEmail) return null;
|
|
return calEvent.organizer?.email || null;
|
|
}),
|
|
}));
|
|
|
|
const createMockCalEvent = (organizerEmail: string) => ({
|
|
organizer: { email: organizerEmail },
|
|
hideOrganizerEmail: false,
|
|
});
|
|
|
|
describe("getReplyToHeader", () => {
|
|
describe("return type", () => {
|
|
it("always returns replyTo as a string, never an array", () => {
|
|
const calEvent = createMockCalEvent("organizer@test.com");
|
|
const result = getReplyToHeader(calEvent as any, ["attendee1@test.com", "attendee2@test.com"]);
|
|
|
|
expect(result).toHaveProperty("replyTo");
|
|
expect(typeof result.replyTo).toBe("string");
|
|
// Should NOT be an array
|
|
expect(Array.isArray(result.replyTo)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("with single email", () => {
|
|
it("returns single email as string", () => {
|
|
const calEvent = createMockCalEvent("organizer@test.com");
|
|
const result = getReplyToHeader(calEvent as any);
|
|
|
|
expect(result).toEqual({ replyTo: "organizer@test.com" });
|
|
});
|
|
|
|
it("returns additionalEmail as string when provided alone", () => {
|
|
const calEvent = createMockCalEvent("organizer@test.com");
|
|
const result = getReplyToHeader(calEvent as any, "additional@test.com", true);
|
|
|
|
expect(result).toEqual({ replyTo: "additional@test.com" });
|
|
});
|
|
});
|
|
|
|
describe("with multiple emails", () => {
|
|
it("returns comma-separated string for multiple emails", () => {
|
|
const calEvent = createMockCalEvent("organizer@test.com");
|
|
const result = getReplyToHeader(calEvent as any, ["attendee1@test.com", "attendee2@test.com"]);
|
|
|
|
expect(result).toEqual({
|
|
replyTo: "attendee1@test.com, attendee2@test.com, organizer@test.com",
|
|
});
|
|
});
|
|
|
|
it("returns comma-separated string when additionalEmails is array", () => {
|
|
const calEvent = createMockCalEvent("organizer@test.com");
|
|
const result = getReplyToHeader(calEvent as any, ["a@test.com", "b@test.com", "c@test.com"], true);
|
|
|
|
expect(result).toEqual({
|
|
replyTo: "a@test.com, b@test.com, c@test.com",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("with no emails", () => {
|
|
it("returns empty object when no emails available", () => {
|
|
const calEvent = { organizer: { email: "" }, hideOrganizerEmail: true };
|
|
const result = getReplyToHeader(calEvent as any, undefined, true);
|
|
|
|
expect(result).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe("SMTP compatibility", () => {
|
|
it("produces RFC 5322 compliant Reply-To header format", () => {
|
|
const calEvent = createMockCalEvent("organizer@test.com");
|
|
const result = getReplyToHeader(calEvent as any, ["a@test.com", "b@test.com"]);
|
|
|
|
// RFC 5322 specifies comma-separated list for multiple addresses
|
|
expect(result.replyTo).toMatch(/^[^,]+, [^,]+, [^,]+$/);
|
|
expect(result.replyTo).not.toContain("[");
|
|
expect(result.replyTo).not.toContain("]");
|
|
});
|
|
});
|
|
});
|