From be309880fd9fcfad47f557c83a340377ede2c404 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Thu, 4 Sep 2025 09:09:38 -0700 Subject: [PATCH] Fix payout reminder cron --- .../api/cron/bounties/notify-partners/route.ts | 2 +- .../api/cron/domains/renewal-reminders/route.ts | 2 +- .../(ee)/api/cron/merge-partner-accounts/route.ts | 2 +- .../charge-succeeded/send-paypal-payouts.ts | 2 +- .../charge-succeeded/send-stripe-payouts.ts | 2 +- .../api/cron/payouts/reminders/partners/route.ts | 15 +-------------- .../payouts/reminders/program-owners/route.ts | 5 +++++ .../app/(ee)/api/stripe/webhook/charge-failed.ts | 4 ++-- .../(ee)/api/stripe/webhook/charge-succeeded.ts | 2 +- .../lib/actions/partners/bulk-approve-partners.ts | 2 +- .../actions/partners/create-bounty-submission.ts | 2 +- .../actions/partners/merge-partner-accounts.ts | 2 +- apps/web/lib/api/domains/claim-dot-link-domain.ts | 2 +- .../api/partners/notify-partner-application.ts | 2 +- .../lib/api/partners/notify-partner-commission.ts | 2 +- .../lib/partners/approve-partner-enrollment.ts | 2 +- apps/web/ui/analytics/feedback/action.ts | 2 +- packages/email/src/index.ts | 3 +-- packages/email/src/resend/client.ts | 4 +--- packages/email/src/resend/subscribe.ts | 4 ++-- packages/email/src/resend/unsubscribe.ts | 4 ++-- 21 files changed, 28 insertions(+), 39 deletions(-) diff --git a/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts b/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts index 0c803bd4c86..b0a6c69e7fd 100644 --- a/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts +++ b/apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts @@ -104,7 +104,7 @@ export async function POST(req: Request) { console.log( `Sending emails to ${programEnrollmentChunk.length} partners: ${programEnrollmentChunk.map(({ partner }) => partner.email).join(", ")}`, ); - await resend?.batch.send( + await resend.batch.send( programEnrollmentChunk.map(({ partner }) => ({ from: VARIANT_TO_FROM_MAP.notifications, to: partner.email!, // coerce the type here because we've already filtered out partners with no email in the prisma query diff --git a/apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts b/apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts index 30be2adb3bf..7053fdd6291 100644 --- a/apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts +++ b/apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts @@ -109,7 +109,7 @@ export async function GET(req: Request) { const reminderDomainsChunks = chunk(reminderDomains, 100); for (const reminderDomainsChunk of reminderDomainsChunks) { - const res = await resend?.batch.send( + const res = await resend.batch.send( reminderDomainsChunk.map(({ workspace, user, domain }) => ({ from: VARIANT_TO_FROM_MAP.notifications, to: user.email!, diff --git a/apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts b/apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts index 2000120b4e4..edf6895114c 100644 --- a/apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts +++ b/apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts @@ -244,7 +244,7 @@ export async function POST(req: Request) { // Make sure the cache is cleared await redis.del(`${CACHE_KEY_PREFIX}:${userId}`); - await resend?.batch.send([ + await resend.batch.send([ { from: VARIANT_TO_FROM_MAP.notifications, to: sourceEmail, diff --git a/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts b/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts index 4546fff7bf8..7bfe8762960 100644 --- a/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts +++ b/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-paypal-payouts.ts @@ -48,7 +48,7 @@ export async function sendPaypalPayouts({ invoiceId }: { invoiceId: string }) { console.log("PayPal batch payout created", batchPayout); - const batchEmails = await resend?.batch.send( + const batchEmails = await resend.batch.send( payouts .filter((payout) => payout.partner.email) .map((payout) => ({ diff --git a/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts b/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts index 78070abf9f0..80c5d86f6f5 100644 --- a/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts +++ b/apps/web/app/(ee)/api/cron/payouts/charge-succeeded/send-stripe-payouts.ts @@ -92,7 +92,7 @@ export async function sendStripePayouts({ invoiceId }: { invoiceId: string }) { await new Promise((resolve) => setTimeout(resolve, 250)); } - const resendBatch = await resend?.batch.send( + const resendBatch = await resend.batch.send( currentInvoicePayouts .filter((p) => p.partner.email) .map((p) => { diff --git a/apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts b/apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts index 7431fa7a135..cd1b82a0629 100644 --- a/apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts +++ b/apps/web/app/(ee)/api/cron/payouts/reminders/partners/route.ts @@ -4,7 +4,7 @@ import { resend } from "@dub/email/resend"; import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants"; import ConnectPayoutReminder from "@dub/email/templates/connect-payout-reminder"; import { prisma } from "@dub/prisma"; -import { chunk, log } from "@dub/utils"; +import { chunk } from "@dub/utils"; import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; @@ -65,19 +65,6 @@ export async function GET(req: Request) { }), ]); - if (!resend) { - await log({ - message: "Resend is not configured, skipping email sending.", - type: "errors", - }); - - console.warn("Resend is not configured, skipping email sending."); - - return NextResponse.json( - "Resend is not configured, skipping email sending.", - ); - } - const partnerProgramMap = new Map< string, { diff --git a/apps/web/app/(ee)/api/cron/payouts/reminders/program-owners/route.ts b/apps/web/app/(ee)/api/cron/payouts/reminders/program-owners/route.ts index 4746892bee1..fbec53f0671 100644 --- a/apps/web/app/(ee)/api/cron/payouts/reminders/program-owners/route.ts +++ b/apps/web/app/(ee)/api/cron/payouts/reminders/program-owners/route.ts @@ -85,6 +85,11 @@ export async function GET(req: Request) { }, }); + // if there are no pending payouts, skip this program + if (!pendingPayout._sum?.amount) { + continue; + } + pendingPayouts.push({ programId: program.id, _sum: pendingPayout._sum, diff --git a/apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts b/apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts index 6cd19e38261..96fdad75fb8 100644 --- a/apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts +++ b/apps/web/app/(ee)/api/stripe/webhook/charge-failed.ts @@ -245,7 +245,7 @@ async function processDomainRenewalInvoice({ invoice }: { invoice: Invoice }) { ); if (workspaceOwners.length > 0) { - await resend?.batch.send( + await resend.batch.send( workspaceOwners.map(({ user }) => ({ from: VARIANT_TO_FROM_MAP.notifications, to: user.email!, @@ -275,7 +275,7 @@ async function processDomainRenewalInvoice({ invoice }: { invoice: Invoice }) { }); if (workspaceOwners.length > 0) { - await resend?.batch.send( + await resend.batch.send( workspaceOwners.map(({ user }) => ({ from: VARIANT_TO_FROM_MAP.notifications, to: user.email!, diff --git a/apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts b/apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts index bf5b3c9feb5..59c5b831bbe 100644 --- a/apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts +++ b/apps/web/app/(ee)/api/stripe/webhook/charge-succeeded.ts @@ -152,7 +152,7 @@ async function processDomainRenewalInvoice({ invoice }: { invoice: Invoice }) { return; } - await resend?.batch.send( + await resend.batch.send( workspaceOwners.map(({ user }) => ({ from: VARIANT_TO_FROM_MAP.notifications, to: user.email!, diff --git a/apps/web/lib/actions/partners/bulk-approve-partners.ts b/apps/web/lib/actions/partners/bulk-approve-partners.ts index 5e70fd5c207..42f63e863bc 100644 --- a/apps/web/lib/actions/partners/bulk-approve-partners.ts +++ b/apps/web/lib/actions/partners/bulk-approve-partners.ts @@ -161,7 +161,7 @@ export const bulkApprovePartnersAction = authActionClient await Promise.allSettled([ // Send approval emails - ...emailChunks.map((emailChunk) => resend?.batch.send(emailChunk)), + ...emailChunks.map((emailChunk) => resend.batch.send(emailChunk)), // Send enrolled webhooks ...updatedEnrollments.map(({ partner, ...enrollment }) => diff --git a/apps/web/lib/actions/partners/create-bounty-submission.ts b/apps/web/lib/actions/partners/create-bounty-submission.ts index ce040de74cd..a14d392dbfb 100644 --- a/apps/web/lib/actions/partners/create-bounty-submission.ts +++ b/apps/web/lib/actions/partners/create-bounty-submission.ts @@ -141,7 +141,7 @@ export const createBountySubmissionAction = authPartnerActionClient }); if (users.length > 0) { - await resend?.batch.send( + await resend.batch.send( users.map((user) => ({ from: VARIANT_TO_FROM_MAP.notifications, to: user.email, diff --git a/apps/web/lib/actions/partners/merge-partner-accounts.ts b/apps/web/lib/actions/partners/merge-partner-accounts.ts index 5b0d5b8d3ec..46967004210 100644 --- a/apps/web/lib/actions/partners/merge-partner-accounts.ts +++ b/apps/web/lib/actions/partners/merge-partner-accounts.ts @@ -171,7 +171,7 @@ const sendTokens = async ({ ], }); - await resend?.batch.send([ + await resend.batch.send([ { from: VARIANT_TO_FROM_MAP.notifications, to: sourceEmail, diff --git a/apps/web/lib/api/domains/claim-dot-link-domain.ts b/apps/web/lib/api/domains/claim-dot-link-domain.ts index 79f889b1d81..0ed50b25c87 100644 --- a/apps/web/lib/api/domains/claim-dot-link-domain.ts +++ b/apps/web/lib/api/domains/claim-dot-link-domain.ts @@ -189,7 +189,7 @@ export const sendDomainClaimedEmails = async ({ })); if (emails.length > 0) { - return await resend?.batch.send(emails); + return await resend.batch.send(emails); } return null; diff --git a/apps/web/lib/api/partners/notify-partner-application.ts b/apps/web/lib/api/partners/notify-partner-application.ts index 6a91b3ebb4d..54e34213e83 100644 --- a/apps/web/lib/api/partners/notify-partner-application.ts +++ b/apps/web/lib/api/partners/notify-partner-application.ts @@ -72,6 +72,6 @@ export async function notifyPartnerApplication({ // Send all emails in batches await Promise.all( - emailChunks.map((emailChunk) => resend?.batch.send(emailChunk)), + emailChunks.map((emailChunk) => resend.batch.send(emailChunk)), ); } diff --git a/apps/web/lib/api/partners/notify-partner-commission.ts b/apps/web/lib/api/partners/notify-partner-commission.ts index a79459a1d3a..5a27d900973 100644 --- a/apps/web/lib/api/partners/notify-partner-commission.ts +++ b/apps/web/lib/api/partners/notify-partner-commission.ts @@ -140,6 +140,6 @@ export async function notifyPartnerCommission({ // Send all emails in batches await Promise.all( - emailChunks.map((emailChunk) => resend?.batch.send(emailChunk)), + emailChunks.map((emailChunk) => resend.batch.send(emailChunk)), ); } diff --git a/apps/web/lib/partners/approve-partner-enrollment.ts b/apps/web/lib/partners/approve-partner-enrollment.ts index 844c684f0cc..09bc6e48a36 100644 --- a/apps/web/lib/partners/approve-partner-enrollment.ts +++ b/apps/web/lib/partners/approve-partner-enrollment.ts @@ -176,7 +176,7 @@ export async function approvePartnerEnrollment({ ...(partnerEmailsToNotify.length ? [ - resend?.batch.send( + resend.batch.send( partnerEmailsToNotify.map((email) => ({ subject: `Your application to join ${program.name} partner program has been approved!`, from: VARIANT_TO_FROM_MAP.notifications, diff --git a/apps/web/ui/analytics/feedback/action.ts b/apps/web/ui/analytics/feedback/action.ts index f077bc9656d..17e26b7d9ae 100644 --- a/apps/web/ui/analytics/feedback/action.ts +++ b/apps/web/ui/analytics/feedback/action.ts @@ -7,7 +7,7 @@ export async function submitFeedback(data: FormData) { const email = data.get("email") as string; const feedback = data.get("feedback") as string; - return await resend?.emails.send({ + return await resend.emails.send({ from: "feedback@dub.co", to: "steven@dub.co", ...(email && { replyTo: email }), diff --git a/packages/email/src/index.ts b/packages/email/src/index.ts index ef035188b2c..a2d197ed261 100644 --- a/packages/email/src/index.ts +++ b/packages/email/src/index.ts @@ -1,10 +1,9 @@ -import { resend } from "./resend"; import { ResendEmailOptions } from "./resend/types"; import { sendViaNodeMailer } from "./send-via-nodemailer"; import { sendEmailViaResend } from "./send-via-resend"; export const sendEmail = async (opts: ResendEmailOptions) => { - if (resend) { + if (process.env.RESEND_API_KEY) { return await sendEmailViaResend(opts); } diff --git a/packages/email/src/resend/client.ts b/packages/email/src/resend/client.ts index 7ed3e93912b..6ed938e8dd0 100644 --- a/packages/email/src/resend/client.ts +++ b/packages/email/src/resend/client.ts @@ -1,5 +1,3 @@ import { Resend } from "resend"; -export const resend = process.env.RESEND_API_KEY - ? new Resend(process.env.RESEND_API_KEY) - : null; +export const resend = new Resend(process.env.RESEND_API_KEY); diff --git a/packages/email/src/resend/subscribe.ts b/packages/email/src/resend/subscribe.ts index 9252aacfd1e..1e291bcfe2e 100644 --- a/packages/email/src/resend/subscribe.ts +++ b/packages/email/src/resend/subscribe.ts @@ -10,9 +10,9 @@ export async function subscribe({ name?: string | null; audience?: keyof typeof RESEND_AUDIENCES; }) { - if (!resend) { + if (!process.env.RESEND_API_KEY) { console.error( - "Resend client is not initialized. This may be due to a missing or invalid RESEND_API_KEY in the .env file. Skipping.", + "No RESEND_API_KEY is set in the environment variables. Skipping.", ); return; } diff --git a/packages/email/src/resend/unsubscribe.ts b/packages/email/src/resend/unsubscribe.ts index 5f98fac9e38..8926504ac03 100644 --- a/packages/email/src/resend/unsubscribe.ts +++ b/packages/email/src/resend/unsubscribe.ts @@ -8,9 +8,9 @@ export async function unsubscribe({ email: string; audience?: keyof typeof RESEND_AUDIENCES; }) { - if (!resend) { + if (!process.env.RESEND_API_KEY) { console.error( - "Resend client is not properly initialized. Skipping operation.", + "No RESEND_API_KEY is set in the environment variables. Skipping.", ); return; }