Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ We love our contributors! Here's how you can contribute:
<img src="https://contrib.rocks/image?repo=dubinc/dub" />
</a>

### Recommended Versions

| Package | Version |
| ------- | -------- |
| node | v23.11.0 |
| pnpm | 9.15.9 |

## Common Issues

- `The table <table-name> does not exist in the current database.` - Run `pnpm prisma:push` push the state of the Prisma schema file to the database without using migrations files.
- The project is not building correctly locally - verify your versions of `node` and `pnpm` match the recommended versions above. Delete all `node_modules`, `.next`, and `.turbo` directories in the `apps` and `packages` directory. You may now reinstall `node_modules` by running `pnpm install` and attempt to rebuild the project with `pnpm build`.

## Repo Activity

![Dub repo activity – generated by Axiom](https://repobeats.axiom.co/api/embed/6ac4c94a89ea20e2e10032b932a128b6d8442e66.svg "Repobeats analytics image")
Expand Down
7 changes: 3 additions & 4 deletions apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { qstash } from "@/lib/cron";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import { sendBatchEmail } from "@dub/email";
import NewBountyAvailable from "@dub/email/templates/new-bounty-available";
import { prisma } from "@dub/prisma";
import { APP_DOMAIN_WITH_NGROK, log } from "@dub/utils";
Expand Down Expand Up @@ -105,9 +104,9 @@ export async function POST(req: Request) {
console.log(
`Sending emails to ${programEnrollments.length} partners: ${programEnrollments.map(({ partner }) => partner.email).join(", ")}`,
);
await resend.batch.send(
await sendBatchEmail(
programEnrollments.map(({ partner }) => ({
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: partner.email!, // coerce the type here because we've already filtered out partners with no email in the prisma query
subject: `New bounty available for ${bounty.program.name}`,
react: NewBountyAvailable({
Expand Down
6 changes: 2 additions & 4 deletions apps/web/app/(ee)/api/cron/domains/renewal-reminders/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { verifyVercelSignature } from "@/lib/cron/verify-vercel";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import { sendBatchEmail } from "@dub/email";
import DomainRenewalReminder from "@dub/email/templates/domain-renewal-reminder";
import { prisma } from "@dub/prisma";
import { chunk, log } from "@dub/utils";
Expand Down Expand Up @@ -109,9 +108,8 @@ 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 sendBatchEmail(
reminderDomainsChunk.map(({ workspace, user, domain }) => ({
from: VARIANT_TO_FROM_MAP.notifications,
to: user.email!,
subject: "Your domain is expiring soon",
variant: "notifications",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(ee)/api/cron/domains/transfer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const sendDomainTransferredEmail = async ({

await sendEmail({
subject: "Domain transfer completed",
email: ownerEmail,
to: ownerEmail,
react: DomainTransferred({
email: ownerEmail,
domain,
Expand Down
45 changes: 20 additions & 25 deletions apps/web/app/(ee)/api/cron/domains/verify/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { markDomainAsDeleted } from "@/lib/api/domains/mark-domain-deleted";
import { limiter } from "@/lib/cron/limiter";
import { sendEmail } from "@dub/email";
import { sendBatchEmail } from "@dub/email";
import DomainDeleted from "@dub/email/templates/domain-deleted";
import InvalidDomain from "@dub/email/templates/invalid-domain";
import { prisma } from "@dub/prisma";
Expand Down Expand Up @@ -125,19 +124,17 @@ export const handleDomainUpdates = async ({
message: `Domain *${domain}* has been invalid for > 30 days andhas links but no link clicks, deleting.`,
type: "cron",
}),
emails.map((email) =>
limiter.schedule(() =>
sendEmail({
subject: `Your domain ${domain} has been deleted`,
sendBatchEmail(
emails.map((email) => ({
subject: `Your domain ${domain} has been deleted`,
to: email,
react: DomainDeleted({
email,
react: DomainDeleted({
email,
domain,
workspaceSlug,
}),
variant: "notifications",
domain,
workspaceSlug,
}),
),
variant: "notifications",
})),
),
]);
}
Expand Down Expand Up @@ -192,20 +189,18 @@ const sendDomainInvalidEmail = async ({
message: `Domain *${domain}* is invalid for ${invalidDays} days, email sent.`,
type: "cron",
}),
emails.map((email) =>
limiter.schedule(() =>
sendEmail({
subject: `Your domain ${domain} needs to be configured`,
sendBatchEmail(
emails.map((email) => ({
subject: `Your domain ${domain} needs to be configured`,
to: email,
react: InvalidDomain({
email,
react: InvalidDomain({
email,
domain,
workspaceSlug,
invalidDays,
}),
variant: "notifications",
domain,
workspaceSlug,
invalidDays,
}),
),
variant: "notifications",
})),
),
prisma.sentEmail.create({
data: {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(ee)/api/cron/import/bitly/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export const importLinksFromBitly = async ({
// send email to user
sendEmail({
subject: `Your Bitly links have been imported!`,
email: ownerEmail,
to: ownerEmail,
react: LinksImported({
email: ownerEmail,
provider: "Bitly",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/(ee)/api/cron/import/csv/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function sendCsvImportEmails({
if (count > 0) {
sendEmail({
subject: `Your CSV links have been imported!`,
email: ownerEmail,
to: ownerEmail,
react: LinksImported({
email: ownerEmail,
provider: "CSV",
Expand All @@ -81,7 +81,7 @@ export async function sendCsvImportEmails({
if (errorLinks.length > 0) {
sendEmail({
subject: `Some CSV links failed to import`,
email: ownerEmail,
to: ownerEmail,
react: LinksImportErrors({
email: ownerEmail,
provider: "CSV",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(ee)/api/cron/import/rebrandly/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export const importLinksFromRebrandly = async ({
// send email to user
sendEmail({
subject: `Your Rebrandly links have been imported!`,
email: ownerEmail,
to: ownerEmail,
react: LinksImported({
email: ownerEmail,
provider: "Rebrandly",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(ee)/api/cron/import/short/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export const importLinksFromShort = async ({
// send email to user
sendEmail({
subject: `Your Short.io links have been imported!`,
email: ownerEmail,
to: ownerEmail,
react: LinksImported({
email: ownerEmail,
provider: "Short.io",
Expand Down
10 changes: 5 additions & 5 deletions apps/web/app/(ee)/api/cron/merge-partner-accounts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
import { storage } from "@/lib/storage";
import { recordLink } from "@/lib/tinybird";
import { redis } from "@/lib/upstash";
import { resend, unsubscribe } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import { sendBatchEmail } from "@dub/email";
import { unsubscribe } from "@dub/email/resend";
import PartnerAccountMerged from "@dub/email/templates/partner-account-merged";
import { prisma } from "@dub/prisma";
import { log, R2_URL } from "@dub/utils";
Expand Down Expand Up @@ -244,9 +244,9 @@ export async function POST(req: Request) {
// Make sure the cache is cleared
await redis.del(`${CACHE_KEY_PREFIX}:${userId}`);

await resend.batch.send([
await sendBatchEmail([
{
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: sourceEmail,
subject: "Your Dub partner accounts are now merged",
react: PartnerAccountMerged({
Expand All @@ -256,7 +256,7 @@ export async function POST(req: Request) {
}),
},
{
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: targetEmail,
subject: "Your Dub partner accounts are now merged",
react: PartnerAccountMerged({
Expand Down
7 changes: 3 additions & 4 deletions apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import { sendBatchEmail } from "@dub/email";
import NewMessageFromProgram from "@dub/email/templates/new-message-from-program";
import { prisma } from "@dub/prisma";
import { NotificationEmailType } from "@dub/prisma/client";
Expand Down Expand Up @@ -102,10 +101,10 @@ export async function POST(req: Request) {

const program = programEnrollment.program;

const { data, error } = await resend.batch.send(
const { data, error } = await sendBatchEmail(
partnerEmailsToNotify.map((email) => ({
subject: `${program.name} sent ${unreadMessages.length === 1 ? "a message" : `${unreadMessages.length} messages`}`,
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: email,
react: NewMessageFromProgram({
program: {
Expand Down
7 changes: 3 additions & 4 deletions apps/web/app/(ee)/api/cron/messages/notify-program/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import { sendBatchEmail } from "@dub/email";
import NewMessageFromPartner from "@dub/email/templates/new-message-from-partner";
import { prisma } from "@dub/prisma";
import { NotificationEmailType } from "@dub/prisma/client";
Expand Down Expand Up @@ -111,10 +110,10 @@ export async function POST(req: Request) {

const { program, partner } = programEnrollment;

const { data, error } = await resend.batch.send(
const { data, error } = await sendBatchEmail(
userEmailsToNotify.map((email) => ({
subject: `${unreadMessages.length === 1 ? "New message from" : `${unreadMessages.length} new messages from`} ${partner.name}`,
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: email,
react: NewMessageFromPartner({
workspaceSlug: program.workspace.slug,
Expand Down
39 changes: 17 additions & 22 deletions apps/web/app/(ee)/api/cron/partner-program-summary/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { getAnalytics } from "@/lib/analytics/get-analytics";
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { qstash } from "@/lib/cron";
import { limiter } from "@/lib/cron/limiter";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
import { verifyVercelSignature } from "@/lib/cron/verify-vercel";
import { sendEmail } from "@dub/email";
import { sendBatchEmail } from "@dub/email";
import PartnerProgramSummary from "@dub/email/templates/partner-program-summary";
import { prisma } from "@dub/prisma";
import { APP_DOMAIN_WITH_NGROK, log } from "@dub/utils";
Expand Down Expand Up @@ -317,26 +316,22 @@ async function handler(req: Request) {

const reportingMonth = format(currentMonth, "MMM yyyy");

await Promise.allSettled(
summary.map(({ partner, ...rest }) => {
limiter.schedule(() =>
sendEmail({
subject: `Your ${reportingMonth} performance report for ${program.name} program`,
email: partner.email!,
react: PartnerProgramSummary({
program,
partner,
...rest,
reportingPeriod: {
month: reportingMonth,
start: currentMonth.toISOString(),
end: endOfMonth(currentMonth).toISOString(),
},
}),
variant: "notifications",
}),
);
}),
await sendBatchEmail(
summary.map(({ partner, ...rest }) => ({
subject: `Your ${reportingMonth} performance report for ${program.name} program`,
to: partner.email!,
react: PartnerProgramSummary({
program,
partner,
...rest,
reportingPeriod: {
month: reportingMonth,
start: currentMonth.toISOString(),
end: endOfMonth(currentMonth).toISOString(),
},
}),
variant: "notifications",
})),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export async function POST(req: Request) {
const sentEmail = await sendEmail({
variant: "notifications",
subject: "Your funds are on their way to your bank",
email: partner.email,
to: partner.email,
react: PartnerPayoutWithdrawalInitiated({
email: partner.email,
payout: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createPayPalBatchPayout } from "@/lib/paypal/create-batch-payout";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import { sendBatchEmail } from "@dub/email";
import PartnerPayoutProcessed from "@dub/email/templates/partner-payout-processed";
import { prisma } from "@dub/prisma";

Expand Down Expand Up @@ -48,11 +47,11 @@ export async function sendPaypalPayouts({ invoiceId }: { invoiceId: string }) {

console.log("PayPal batch payout created", batchPayout);

const batchEmails = await resend.batch.send(
const batchEmails = await sendBatchEmail(
payouts
.filter((payout) => payout.partner.email)
.map((payout) => ({
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: payout.partner.email!,
subject: "You've been paid!",
react: PartnerPayoutProcessed({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createStripeTransfer } from "@/lib/partners/create-stripe-transfer";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import { sendBatchEmail } from "@dub/email";
import PartnerPayoutProcessed from "@dub/email/templates/partner-payout-processed";
import { prisma } from "@dub/prisma";
import { Prisma } from "@prisma/client";
Expand Down Expand Up @@ -92,12 +91,12 @@ export async function sendStripePayouts({ invoiceId }: { invoiceId: string }) {
await new Promise((resolve) => setTimeout(resolve, 250));
}

const resendBatch = await resend.batch.send(
const resendBatch = await sendBatchEmail(
currentInvoicePayouts
.filter((p) => p.partner.email)
.map((p) => {
return {
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: p.partner.email!,
subject: "You've been paid!",
react: PartnerPayoutProcessed({
Expand Down
6 changes: 3 additions & 3 deletions apps/web/app/(ee)/api/cron/payouts/process/process-payouts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { calculatePayoutFeeForMethod } from "@/lib/payment-methods";
import { stripe } from "@/lib/stripe";
import { createFxQuote } from "@/lib/stripe/create-fx-quote";
import { PlanProps } from "@/lib/types";
import { sendBatchEmail } from "@dub/email";
import { resend } from "@dub/email/resend";
import { VARIANT_TO_FROM_MAP } from "@dub/email/resend/constants";
import PartnerPayoutConfirmed from "@dub/email/templates/partner-payout-confirmed";
import { prisma } from "@dub/prisma";
import { chunk, currencyFormatter, log } from "@dub/utils";
Expand Down Expand Up @@ -236,9 +236,9 @@ export async function processPayouts({
);

for (const payoutChunk of payoutChunks) {
await resend.batch.send(
await sendBatchEmail(
payoutChunk.map((payout) => ({
from: VARIANT_TO_FROM_MAP.notifications,
variant: "notifications",
to: payout.partner.email!,
subject: "You've got money coming your way!",
react: PartnerPayoutConfirmed({
Expand Down
Loading