diff --git a/apps/web/app/(ee)/api/cron/cleanup/expired-fraud-groups/route.ts b/apps/web/app/(ee)/api/cron/cleanup/expired-fraud-groups/route.ts index fa2e8886308..12aae356557 100644 --- a/apps/web/app/(ee)/api/cron/cleanup/expired-fraud-groups/route.ts +++ b/apps/web/app/(ee)/api/cron/cleanup/expired-fraud-groups/route.ts @@ -12,12 +12,6 @@ const BATCH_SIZE = 250; // Runs once every day at 02:30:00 AM UTC (30 2 * * *) // POST /api/cron/cleanup/expired-fraud-groups export const POST = withCron(async () => { - // TODO: remove this after July 9th, 2026 - // skip cron if it's before July 9th, 2026 - if (new Date() < new Date("2026-07-09")) { - return logAndRespond("Skipping cron because it's before July 9th, 2026."); - } - let expiredCount = 0; while (true) { const { count } = await prisma.fraudEventGroup.updateMany({ diff --git a/apps/web/lib/email/email-templates-map.ts b/apps/web/lib/email/email-templates-map.ts index fb2ccc9f251..674b007fb9f 100644 --- a/apps/web/lib/email/email-templates-map.ts +++ b/apps/web/lib/email/email-templates-map.ts @@ -1,7 +1,6 @@ import BountyApproved from "@dub/email/templates/bounty-approved"; import IdentityVerificationAnnouncement from "@dub/email/templates/broadcasts/identity-verification-announcement"; import DubLaunchWeekDay5 from "@dub/email/templates/broadcasts/launch-week-day-5"; -import RiskCenterChangeAnnouncement from "@dub/email/templates/broadcasts/risk-center-change-announcement"; import ConnectPayoutReminder from "@dub/email/templates/connect-payout-reminder"; import ConnectPlatformsReminder from "@dub/email/templates/connect-platforms-reminder"; import PartnerBanned from "@dub/email/templates/partner-banned"; @@ -34,5 +33,4 @@ export const EMAIL_TEMPLATES_MAP = { // ProgramMarketplaceAnnouncement, // StablecoinPayoutsAnnouncement, DubLaunchWeekDay5, - RiskCenterChangeAnnouncement, } as const; diff --git a/apps/web/scripts/send-risk-center-change-announcement.ts b/apps/web/scripts/send-risk-center-change-announcement.ts deleted file mode 100644 index db69a0e8284..00000000000 --- a/apps/web/scripts/send-risk-center-change-announcement.ts +++ /dev/null @@ -1,152 +0,0 @@ -import "dotenv-flow/config"; - -import { prisma } from "@/lib/prisma"; -import type RiskCenterChangeAnnouncement from "@dub/email/templates/broadcasts/risk-center-change-announcement"; -import { FRAUD_RULES_BY_TYPE } from "../lib/api/fraud/constants"; -import { queueBatchEmail } from "../lib/email/queue-batch-email"; - -const BATCH_SIZE = 50; - -async function main() { - let cursor: string | undefined; - - while (true) { - const programs = await prisma.program.findMany({ - where: { - workspace: { - plan: { - in: ["advanced", "enterprise"], - }, - }, - fraudEventGroups: { - some: { - status: "pending", - }, - }, - }, - select: { - id: true, - name: true, - slug: true, - workspace: { - select: { - slug: true, - users: { - select: { - user: { - select: { - email: true, - }, - }, - }, - where: { - user: { - isMachine: false, - email: { - not: null, - }, - }, - }, - }, - }, - }, - fraudEventGroups: { - where: { - status: "pending", - }, - select: { - id: true, - type: true, - eventCount: true, - partner: { - select: { - name: true, - image: true, - }, - }, - }, - }, - }, - take: BATCH_SIZE, - ...(cursor && { - skip: 1, - cursor: { - id: cursor, - }, - }), - orderBy: { - id: "asc", - }, - }); - - if (programs.length === 0) { - console.log("No more programs to notify."); - break; - } - - console.log(`Processing ${programs.length} programs...`); - - for (const program of programs) { - try { - if (program.fraudEventGroups.length === 0) { - continue; - } - - const users = program.workspace.users.map(({ user }) => user); - - if (users.length === 0) { - console.log(`No owners found for program ${program.name}, skipping.`); - continue; - } - - const transformedFraudGroups = program.fraudEventGroups.map( - ({ id, type, eventCount, partner }) => ({ - id, - name: FRAUD_RULES_BY_TYPE[type].name, - count: eventCount, - partner, - }), - ); - - await queueBatchEmail( - users.map((user) => ({ - to: user.email!, - subject: `[Action Required]: Review unresolved risk events for ${program.name}`, - variant: "notifications", - templateName: "RiskCenterChangeAnnouncement", - templateProps: { - email: user.email!, - workspace: program.workspace, - program: { - name: program.name, - }, - fraudGroups: transformedFraudGroups, - }, - })), - { - idempotencyKey: `risk-center-announcement/${program.id}`, - }, - ); - - console.log( - `Queued emails for program ${program.name} (${users.length} users(s))`, - ); - } catch (error) { - console.error( - `Error sending email for program ${program.name}: ${error.message}`, - ); - continue; - } - } - - if (programs.length < BATCH_SIZE) { - break; - } - - cursor = programs[programs.length - 1].id; - } - - console.log("Done."); -} - -main(); diff --git a/packages/email/src/templates/broadcasts/risk-center-change-announcement.tsx b/packages/email/src/templates/broadcasts/risk-center-change-announcement.tsx deleted file mode 100644 index bdcf08653b7..00000000000 --- a/packages/email/src/templates/broadcasts/risk-center-change-announcement.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { DUB_WORDMARK, OG_AVATAR_URL } from "@dub/utils"; -import { - Body, - Column, - Container, - Head, - Heading, - Html, - Img, - Link, - Preview, - Row, - Section, - Tailwind, - Text, -} from "@react-email/components"; -import { Footer } from "../../components/footer"; - -const MAX_DISPLAYED_GROUPS = 5; - -export default function RiskCenterChangeAnnouncement({ - email = "panic@thedis.co", - workspace = { - slug: "acme", - }, - program = { - name: "Acme", - }, - fraudGroups = [ - { - id: "frg_1KBEY53HESGDK7RAPX960FEWA", - name: "Customer email match", - count: 2, - partner: { - name: "Lauren Anderson", - image: `${OG_AVATAR_URL}Lauren Anderson`, - }, - }, - { - id: "frg_1KBEZRKC1GB716J5AKV62MK8H", - name: "Referral source", - count: 1, - partner: { - name: "Charlie Anderson", - image: `${OG_AVATAR_URL}Charlie Anderson`, - }, - }, - ], -}: { - email: string; - workspace: { - slug: string; - }; - program: { - name: string; - }; - fraudGroups: { - id: string; - name: string; - count: number; - partner: { - name: string; - image: string | null; - }; - }[]; -}) { - const displayedGroups = fraudGroups.slice(0, MAX_DISPLAYED_GROUPS); - const remainingCount = fraudGroups.length - MAX_DISPLAYED_GROUPS; - const hiddenCount = Math.max(0, remainingCount); - - return ( - - - - - - Risk events now expire after 30 days if left unresolved, please review - them before they expire. - - - - -
- Dub -
- - - Important updates to your Dub program - - - - We've made some important changes to the fraud detection tools for - your {program.name}{" "} - program on Dub that we wanted to let you know about. - - - - Unresolved risk events will now{" "} - - automatically expire after 30 days - {" "} - if no new activity is detected. This is to ensure there are no - delay in partner payouts due to unresolved risk events. - - - - You currently have pending risk events for your program – please - review them before they expire: - - -
- {displayedGroups.map((group, index) => { - const isLastDisplayedItem = - index === displayedGroups.length - 1; - const shouldShowBottomBorder = !isLastDisplayedItem; - return ( - - - - - {group.partner.name} - - - - - {group.partner.name} - - -
- - {group.name} - - {group.count > 1 && ( - - {group.count} - - )} -
-
- - - - View - - -
-
-
- ); - })} - - {hiddenCount > 0 && ( - - - - Plus {hiddenCount} more - - - - )} -
- - - To give you more time to review them, we've extended the - expiration period for all events to July 9th, 2026 (30 days from - now). - - - - Last but not least, the{" "} - Fraud Detection tab has - been renamed to{" "} - Risk Center. All existing - events, rules, and settings remain unchanged – just the names that - are different. - - -
- - Go to Risk Center - -
- -