+
+
+
+ Your contribution š
+
+
+ Thanks to customers like you, we had an incredible year as well,
+ seeing record activity and link clicks.
+
+ {dubStats.map((stat) => (
+
+ ā {stat.item}{" "}
+
+ ({stat.increase})
+
+
+ ))}
+
+
+
+
+
+ What we shipped š¢
+
+
+ Here's a look back at what we shipped in 2024.
+
+
+ {shippedItems.map((item) => (
+
+
+
+ {item.title}
+
+
+
+ {item.cta.text}
+
+
+ ))}
+
+
+
+
+ Thank you again, and happy holidays!
+
+
+
+ and everyone at the Dub team š
+
+
+
+
+
+
+
+ );
+}
+
+const StatCard = ({
+ title,
+ value,
+}: {
+ title: string;
+ value: number | string;
+}) => {
+ return (
+
-
+ {workspace.logo && (
+
+ )}
{workspace.name}
@@ -349,7 +355,7 @@ const SectionHeaderBackground = () => {
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
-
+
@@ -412,7 +418,7 @@ const SectionHeaderBackground = () => {
width="563"
height="285"
filterUnits="userSpaceOnUse"
- color-interpolation-filters="sRGB"
+ colorInterpolationFilters="sRGB"
>
{
y2="111"
gradientUnits="userSpaceOnUse"
>
-
-
+
+ {
gradientUnits="userSpaceOnUse"
gradientTransform="translate(241.5 64.5) rotate(-159.856) scale(84.7412 60.41)"
>
-
-
-
-
-
-
+
+
+
+
+
+
From 1f6682243e35d6729154b0a32ced268e27f5017e Mon Sep 17 00:00:00 2001
From: Steven Tey
Date: Mon, 30 Dec 2024 07:00:21 -0800
Subject: [PATCH 05/13] small fixes
---
apps/web/app/api/cron/year-in-review/route.ts | 4 +++-
apps/web/emails/dub-wrapped.tsx | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/apps/web/app/api/cron/year-in-review/route.ts b/apps/web/app/api/cron/year-in-review/route.ts
index 050e7802b7..fcf8c33768 100644
--- a/apps/web/app/api/cron/year-in-review/route.ts
+++ b/apps/web/app/api/cron/year-in-review/route.ts
@@ -141,7 +141,9 @@ export async function POST(req: Request) {
body: {},
});
- return new Response("Jobs sent.");
+ return new Response(
+ `Sent ${emailData.length} emails to ${yearInReviews.length} workspaces!`,
+ );
} catch (error) {
return handleAndReturnErrorResponse(error);
}
diff --git a/apps/web/emails/dub-wrapped.tsx b/apps/web/emails/dub-wrapped.tsx
index ccbe77c751..0ec2f26800 100644
--- a/apps/web/emails/dub-wrapped.tsx
+++ b/apps/web/emails/dub-wrapped.tsx
@@ -420,7 +420,7 @@ const SectionHeaderBackground = () => {
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
-
+
Date: Mon, 30 Dec 2024 07:02:54 -0800
Subject: [PATCH 06/13] Update dub-wrapped.tsx
---
apps/web/emails/dub-wrapped.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/web/emails/dub-wrapped.tsx b/apps/web/emails/dub-wrapped.tsx
index 0ec2f26800..f7eb7875d9 100644
--- a/apps/web/emails/dub-wrapped.tsx
+++ b/apps/web/emails/dub-wrapped.tsx
@@ -171,6 +171,7 @@ export default function DubWrapped({
src={workspace.logo}
height="36"
alt={workspace.name}
+ className="rounded-lg"
/>
)}
From 2b2695893e65d332c4874f81b0c351053fdda715 Mon Sep 17 00:00:00 2001
From: Steven Tey
Date: Mon, 30 Dec 2024 07:06:57 -0800
Subject: [PATCH 07/13] update
---
apps/web/app/api/cron/year-in-review/route.ts | 3 +--
apps/web/scripts/dub-wrapped.ts | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/apps/web/app/api/cron/year-in-review/route.ts b/apps/web/app/api/cron/year-in-review/route.ts
index fcf8c33768..32d5aba981 100644
--- a/apps/web/app/api/cron/year-in-review/route.ts
+++ b/apps/web/app/api/cron/year-in-review/route.ts
@@ -63,8 +63,7 @@ export async function POST(req: Request) {
workspaceId: workspace.id,
email: {
from: "Steven from Dub.co ",
- // to: user.email!,
- to: "delivered@resend.dev",
+ to: user.email!,
replyToFromEmail: true,
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.",
diff --git a/apps/web/scripts/dub-wrapped.ts b/apps/web/scripts/dub-wrapped.ts
index 4a6423525c..883cc7e698 100644
--- a/apps/web/scripts/dub-wrapped.ts
+++ b/apps/web/scripts/dub-wrapped.ts
@@ -48,7 +48,7 @@ async function main() {
id: "desc",
},
},
- take: 10,
+ take: 20,
})
// only get projects with more than 5 links created in 2024
.then((data) => data.filter(({ _count }) => _count.id > 5));
From 5ddee9135bfe149e6c41a8c99887277d098e06fe Mon Sep 17 00:00:00 2001
From: Steven Tey
Date: Mon, 30 Dec 2024 07:48:18 -0800
Subject: [PATCH 08/13] Update dub-wrapped.tsx
---
apps/web/emails/dub-wrapped.tsx | 36 ++++++++++++++++-----------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/apps/web/emails/dub-wrapped.tsx b/apps/web/emails/dub-wrapped.tsx
index f7eb7875d9..8f67a0a182 100644
--- a/apps/web/emails/dub-wrapped.tsx
+++ b/apps/web/emails/dub-wrapped.tsx
@@ -95,20 +95,20 @@ export default function DubWrapped({
}) {
const dubStats = [
{
- item: "125M clicks tracked",
+ item: "126M clicks tracked",
increase: "+900%",
},
{
- item: "715K links created",
+ item: "700K links created",
increase: "+400%",
},
{
- item: "65K new users",
- increase: "+250%",
+ item: "56K new users",
+ increase: "+360%",
},
{
- item: "4.4K domains added",
- increase: "+400%",
+ item: "5.5K custom domains",
+ increase: "+500%",
},
];
@@ -116,7 +116,7 @@ export default function DubWrapped({
{
title: "Free .LINK domains on all paid plans",
description:
- "We partnered with Nova Registry to offer a 1-year free .link domain to all paying Dub customers. By using a custom domain, you get 30% higher click-through rates.",
+ "We partnered with Nova Registry to offer a 1-year free .link custom domain to all paying Dub customers. By using a custom domain, you get 30% higher click-through rates and better brand recognition.",
image: "https://assets.dub.co/blog/free-dot-link.jpg",
cta: {
text: "Read the announcement",
@@ -134,13 +134,13 @@ export default function DubWrapped({
},
},
{
- title: "Dub Integrations",
+ title: "Dub API General Availability",
description:
- "We added integrations with Slack, Notion, Google Sheets and Google Docs. Our API also went GA",
- image: "https://assets.dub.co/blog/integrations.jpg",
+ "Our Dub API went GA, allowing you to build your powerful integrations with Dub. We also launched native SDKs in 5 different languages: TypeScript, Python, Ruby, PHP, and Go.",
+ image: "https://assets.dub.co/blog/dub-api.jpg",
cta: {
text: "Read the announcement",
- href: "https://dub.co/blog/introducing-dub-integrations",
+ href: "https://dub.co/blog/announcing-dub-api",
},
},
];
@@ -159,8 +159,8 @@ export default function DubWrapped({
Dub Year in Review š
- 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.
+ As we put a wrap on 2024, we wanted to say thank you for your
+ support! Here's a look back at your activity in 2024:
@@ -204,7 +204,7 @@ export default function DubWrapped({
Thanks to customers like you, we had an incredible year as well,
- seeing record activity and link clicks.
+ seeing record activity and link clicks:
{dubStats.map((stat) => (
))}
@@ -229,7 +229,7 @@ export default function DubWrapped({
What we shipped š¢
- Here's a look back at what we shipped in 2024.
+ Here's a rundown of what we shipped in 2024:
{shippedItems.map((item) => (
@@ -266,7 +266,7 @@ export default function DubWrapped({
className="max-w-[200px]"
/>
- and everyone at the Dub team š
+ and the Dub team š
@@ -285,7 +285,7 @@ const StatCard = ({
value: number | string;
}) => {
return (
-
+
{title}
{typeof value === "number" ? nFormatter(value, { full: true }) : value}
From 296369956fe7905d7ae70298fcda5c1858314e60 Mon Sep 17 00:00:00 2001
From: Steven Tey
Date: Mon, 30 Dec 2024 09:19:18 -0800
Subject: [PATCH 09/13] finalize
---
apps/web/app/api/cron/year-in-review/route.ts | 91 +++++----
apps/web/emails/dub-wrapped.tsx | 187 +++++-------------
2 files changed, 96 insertions(+), 182 deletions(-)
diff --git a/apps/web/app/api/cron/year-in-review/route.ts b/apps/web/app/api/cron/year-in-review/route.ts
index 32d5aba981..b8282a614b 100644
--- a/apps/web/app/api/cron/year-in-review/route.ts
+++ b/apps/web/app/api/cron/year-in-review/route.ts
@@ -12,8 +12,9 @@ const BATCH_SIZE = 100;
// POST /api/cron/year-in-review
export async function POST(req: Request) {
try {
- // const body = await req.json();
- // await verifyQstashSignature(req, body);
+ if (process.env.VERCEL === "1") {
+ return new Response("Not available in production.");
+ }
if (!resend) {
return new Response("Resend not initialized. Skipping...");
@@ -49,7 +50,7 @@ export async function POST(req: Request) {
},
},
},
- take: 5,
+ take: 100,
});
if (yearInReviews.length === 0) {
@@ -58,34 +59,40 @@ export async function POST(req: Request) {
const emailData = yearInReviews.flatMap(
({ workspace, totalClicks, totalLinks, topCountries, topLinks }) =>
- workspace.users.map(({ user }) => {
- return {
- workspaceId: workspace.id,
- email: {
- from: "Steven from Dub.co ",
- to: user.email!,
- replyToFromEmail: true,
- 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,
- }),
- },
- };
- }),
+ workspace.users
+ .map(({ user }) => {
+ if (!user.email) {
+ return null;
+ }
+
+ return {
+ workspaceId: workspace.id,
+ email: {
+ from: "Steven from Dub.co ",
+ to: user.email,
+ reply_to: "support@dub.co",
+ 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) {
@@ -101,7 +108,7 @@ export async function POST(req: Request) {
console.log(
`šØ Recipients:`,
- batch.map((b) => b?.email),
+ batch.map(({ email }) => email.to),
);
if (batch.length === 0) {
@@ -112,20 +119,16 @@ export async function POST(req: Request) {
batch.map(({ email }) => email),
);
- console.log("š ~ error:", error);
console.log("š ~ data:", data);
-
- await new Promise((resolve) => setTimeout(resolve, 3000));
+ if (error) {
+ console.log("š ~ error:", error);
+ }
}
- // TODO:
- // Error handling
-
await prisma.yearInReview.updateMany({
where: {
- year: 2024,
- workspaceId: {
- in: yearInReviews.map(({ workspaceId }) => workspaceId),
+ id: {
+ in: yearInReviews.map(({ id }) => id),
},
},
data: {
@@ -133,9 +136,13 @@ export async function POST(req: Request) {
},
});
+ console.log(
+ `Sent ${emailData.length} emails to ${yearInReviews.length} workspaces!`,
+ );
+
await qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/year-in-review`,
- delay: 5,
+ delay: 3,
method: "POST",
body: {},
});
diff --git a/apps/web/emails/dub-wrapped.tsx b/apps/web/emails/dub-wrapped.tsx
index 8f67a0a182..e6256c6078 100644
--- a/apps/web/emails/dub-wrapped.tsx
+++ b/apps/web/emails/dub-wrapped.tsx
@@ -120,7 +120,7 @@ export default function DubWrapped({
image: "https://assets.dub.co/blog/free-dot-link.jpg",
cta: {
text: "Read the announcement",
- href: "https://dub.co/blog/introducing-free-domains",
+ href: "https://ship.dub.co/free-domains",
},
},
{
@@ -130,13 +130,13 @@ export default function DubWrapped({
image: "https://assets.dub.co/changelog/new-dashboard.jpg",
cta: {
text: "Read the announcement",
- href: "https://dub.co/blog/new-link-builder",
+ href: "https://ship.dub.co/builder",
},
},
{
title: "Dub API General Availability",
description:
- "Our Dub API went GA, allowing you to build your powerful integrations with Dub. We also launched native SDKs in 5 different languages: TypeScript, Python, Ruby, PHP, and Go.",
+ "Our Dub API went GA, allowing you to build your powerful integrations with Dub. We also launched native SDKs in 5 different languages: TypeScript, Python, Ruby, PHP, and Go.",
image: "https://assets.dub.co/blog/dub-api.jpg",
cta: {
text: "Read the announcement",
@@ -148,7 +148,11 @@ export default function DubWrapped({
return (
- Dub Wrapped
+
+ In 2024, you created {nFormatter(stats["Total Links"], { full: true })}{" "}
+ links on Dub and got {nFormatter(stats["Total Clicks"], { full: true })}{" "}
+ clicks.
+
@@ -164,27 +168,31 @@ export default function DubWrapped({
-
+ You can also check out more updates on our{" "}
+
+ blog
+ {" "}
+ and{" "}
+
+ changelog
+
+ .
+
+
Thank you again, and happy holidays!
{
return (
-