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
54 changes: 54 additions & 0 deletions lib/apify/__tests__/sendApifyWebhookEmail.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { sendApifyWebhookEmail } from "@/lib/apify/sendApifyWebhookEmail";
import { RECOUP_FROM_EMAIL } from "@/lib/const";

vi.mock("@/lib/ai/generateText", () => ({
default: vi.fn(async () => ({ text: "<p>body</p>" })),
}));
const sendEmailWithResend = vi.fn();
vi.mock("@/lib/emails/sendEmail", () => ({
sendEmailWithResend: (...a: unknown[]) => sendEmailWithResend(...a),
}));

const PROFILE = {
fullName: "Ashnikko",
username: "ashnikko",
url: "https://instagram.com/ashnikko",
profilePicUrl: "https://example.com/pic.jpg",
biography: "bio",
followersCount: 100,
followsCount: 10,
latestPosts: [],
} as never;

beforeEach(() => {
vi.clearAllMocks();
sendEmailWithResend.mockResolvedValue({ id: "email-1" });
});

describe("sendApifyWebhookEmail", () => {
it("BCCs recipients so no account can see another's address (chat#1855)", async () => {
const recipients = [
"manager@customer-a.com",
"label@customer-b.com",
"internal@recoupable.com",
];
await sendApifyWebhookEmail(PROFILE, recipients);
const payload = sendEmailWithResend.mock.calls[0][0] as Record<string, unknown>;
expect(payload.bcc).toEqual(recipients);
expect(payload.to).toEqual([RECOUP_FROM_EMAIL]);
});

it("never carries more than our own address in to/cc, regardless of recipient count", async () => {
const recipients = Array.from({ length: 25 }, (_, i) => `user${i}@example.com`);
await sendApifyWebhookEmail(PROFILE, recipients);
const payload = sendEmailWithResend.mock.calls[0][0] as Record<string, unknown>;
const visible = [payload.to, payload.cc].flat().filter(Boolean);
expect(visible).toEqual([RECOUP_FROM_EMAIL]);
});

it("returns null and sends nothing when there are no recipients", async () => {
expect(await sendApifyWebhookEmail(PROFILE, [])).toBeNull();
expect(sendEmailWithResend).not.toHaveBeenCalled();
});
});
6 changes: 5 additions & 1 deletion lib/apify/sendApifyWebhookEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ Latest Posts: ${(Array.isArray(profile.latestPosts) ? profile.latestPosts : []).
prompt,
});

// Recipients span multiple customer accounts (the social's watchers are
// resolved cross-tenant), so they must NEVER share a visible To: line —
// BCC only, with ourselves as the required To: (chat#1855).
return await sendEmailWithResend({
from: RECOUP_FROM_EMAIL,
to: emails,
to: [RECOUP_FROM_EMAIL],
bcc: emails,
subject: `${profile.fullName ?? profile.username ?? "Your artist"} has new posts on Instagram`,
html: text,
});
Expand Down
Loading