-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1845 from dubinc/wrapped
Dub Wrapped
- Loading branch information
Showing
7 changed files
with
665 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { handleAndReturnErrorResponse } from "@/lib/api/errors"; | ||
import { qstash } from "@/lib/cron"; | ||
import { resend } from "@/lib/resend"; | ||
import { prisma } from "@dub/prisma"; | ||
import { APP_DOMAIN_WITH_NGROK } from "@dub/utils"; | ||
import DubWrapped from "emails/dub-wrapped"; | ||
|
||
export const dynamic = "force-dynamic"; | ||
|
||
const BATCH_SIZE = 100; | ||
|
||
// POST /api/cron/year-in-review | ||
export async function POST(req: Request) { | ||
try { | ||
if (process.env.VERCEL === "1") { | ||
return new Response("Not available in production."); | ||
} | ||
|
||
if (!resend) { | ||
return new Response("Resend not initialized. Skipping..."); | ||
} | ||
|
||
const yearInReviews = await prisma.yearInReview.findMany({ | ||
where: { | ||
sentAt: null, | ||
year: 2024, | ||
}, | ||
select: { | ||
id: true, | ||
workspaceId: true, | ||
topCountries: true, | ||
topLinks: true, | ||
totalClicks: true, | ||
totalLinks: true, | ||
workspace: { | ||
select: { | ||
id: true, | ||
name: true, | ||
slug: true, | ||
logo: true, | ||
users: { | ||
select: { | ||
user: { | ||
select: { | ||
email: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
take: 100, | ||
}); | ||
|
||
if (yearInReviews.length === 0) { | ||
return new Response("No jobs found. Skipping..."); | ||
} | ||
|
||
const emailData = yearInReviews.flatMap( | ||
({ workspace, totalClicks, totalLinks, topCountries, topLinks }) => | ||
workspace.users | ||
.map(({ user }) => { | ||
if (!user.email) { | ||
return null; | ||
} | ||
|
||
return { | ||
workspaceId: workspace.id, | ||
email: { | ||
from: "Steven from Dub.co <[email protected]>", | ||
to: user.email, | ||
reply_to: "[email protected]", | ||
subject: "Dub Year in Review 🎊", | ||
text: "Thank you for your support and here's to another year of your activity on Dub! Here's a look back at your activity in 2024.", | ||
react: DubWrapped({ | ||
email: user.email, | ||
workspace: { | ||
logo: workspace.logo, | ||
name: workspace.name, | ||
slug: workspace.slug, | ||
}, | ||
stats: { | ||
"Total Links": totalLinks, | ||
"Total Clicks": totalClicks, | ||
}, | ||
// @ts-ignore | ||
topLinks, | ||
// @ts-ignore | ||
topCountries, | ||
}), | ||
}, | ||
}; | ||
}) | ||
.filter((data) => data !== null), | ||
); | ||
|
||
if (emailData.length === 0) { | ||
return new Response("No email data found. Skipping..."); | ||
} | ||
|
||
for (let i = 0; i < emailData.length; i += BATCH_SIZE) { | ||
const batch = emailData.slice(i, i + BATCH_SIZE); | ||
|
||
console.log( | ||
`\n🚀 Sending batch ${Math.floor(i / BATCH_SIZE) + 1} of ${Math.ceil(emailData.length / BATCH_SIZE)}`, | ||
); | ||
|
||
console.log( | ||
`📨 Recipients:`, | ||
// @ts-ignore | ||
batch.map((b) => b.email.to), | ||
); | ||
|
||
if (batch.length === 0) { | ||
continue; | ||
} | ||
|
||
const { data, error } = await resend.batch.send( | ||
// @ts-ignore | ||
batch.map((b) => b.email), | ||
); | ||
|
||
console.log("🚀 ~ data:", data); | ||
if (error) { | ||
console.log("🚀 ~ error:", error); | ||
} | ||
} | ||
|
||
await prisma.yearInReview.updateMany({ | ||
where: { | ||
id: { | ||
in: yearInReviews.map(({ id }) => id), | ||
}, | ||
}, | ||
data: { | ||
sentAt: new Date(), | ||
}, | ||
}); | ||
|
||
console.log( | ||
`Sent ${emailData.length} emails to ${yearInReviews.length} workspaces!`, | ||
); | ||
|
||
await qstash.publishJSON({ | ||
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/year-in-review`, | ||
delay: 3, | ||
method: "POST", | ||
body: {}, | ||
}); | ||
|
||
return new Response( | ||
`Sent ${emailData.length} emails to ${yearInReviews.length} workspaces!`, | ||
); | ||
} catch (error) { | ||
return handleAndReturnErrorResponse(error); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.