mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-05 18:26:40 +00:00
remove resend
This commit is contained in:
@@ -1,16 +0,0 @@
|
|||||||
export enum AudienceIds {
|
|
||||||
SignUps = "",
|
|
||||||
Subscribed = "",
|
|
||||||
Churned = "",
|
|
||||||
Newsletter = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
let resend;
|
|
||||||
export default resend;
|
|
||||||
|
|
||||||
export async function moveEmailToAudience(
|
|
||||||
email: string,
|
|
||||||
audienceId: AudienceIds
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Resend } from "resend";
|
|
||||||
import privateConfig from "#private/lib/config";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
|
|
||||||
export enum AudienceIds {
|
|
||||||
SignUps = "6c4e77b2-0851-4bd6-bac8-f51f91360f1a",
|
|
||||||
Subscribed = "870b43fd-387f-44de-8fc1-707335f30b20",
|
|
||||||
Churned = "f3ae92bd-2fdb-4d77-8746-2118afd62549",
|
|
||||||
Newsletter = "5500c431-191c-42f0-a5d4-8b6d445b4ea0"
|
|
||||||
}
|
|
||||||
|
|
||||||
const resend = new Resend(
|
|
||||||
privateConfig.getRawPrivateConfig().server.resend_api_key || "missing"
|
|
||||||
);
|
|
||||||
|
|
||||||
export default resend;
|
|
||||||
|
|
||||||
export async function moveEmailToAudience(
|
|
||||||
email: string,
|
|
||||||
audienceId: AudienceIds
|
|
||||||
) {
|
|
||||||
if (process.env.ENVIRONMENT !== "prod") {
|
|
||||||
logger.debug(
|
|
||||||
`Skipping moving email ${email} to audience ${audienceId} in non-prod environment`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { error, data } = await retryWithBackoff(async () => {
|
|
||||||
const { data, error } = await resend.contacts.create({
|
|
||||||
email,
|
|
||||||
unsubscribed: false,
|
|
||||||
audienceId
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
throw new Error(
|
|
||||||
`Error adding email ${email} to audience ${audienceId}: ${error}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return { error, data };
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
logger.error(
|
|
||||||
`Error adding email ${email} to audience ${audienceId}: ${error}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
logger.debug(
|
|
||||||
`Added email ${email} to audience ${audienceId} with contact ID ${data.id}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const otherAudiences = Object.values(AudienceIds).filter(
|
|
||||||
(id) => id !== audienceId
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const otherAudienceId of otherAudiences) {
|
|
||||||
const { error, data } = await retryWithBackoff(async () => {
|
|
||||||
const { data, error } = await resend.contacts.remove({
|
|
||||||
email,
|
|
||||||
audienceId: otherAudienceId
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
throw new Error(
|
|
||||||
`Error removing email ${email} from audience ${otherAudienceId}: ${error}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return { error, data };
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
logger.error(
|
|
||||||
`Error removing email ${email} from audience ${otherAudienceId}: ${error}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
logger.info(
|
|
||||||
`Removed email ${email} from audience ${otherAudienceId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RetryOptions = {
|
|
||||||
retries?: number;
|
|
||||||
initialDelayMs?: number;
|
|
||||||
factor?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function retryWithBackoff<T>(
|
|
||||||
fn: () => Promise<T>,
|
|
||||||
options: RetryOptions = {}
|
|
||||||
): Promise<T> {
|
|
||||||
const { retries = 5, initialDelayMs = 500, factor = 2 } = options;
|
|
||||||
|
|
||||||
let attempt = 0;
|
|
||||||
let delay = initialDelayMs;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
return await fn();
|
|
||||||
} catch (err) {
|
|
||||||
attempt++;
|
|
||||||
|
|
||||||
if (attempt > retries) throw err;
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
||||||
delay *= factor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ import { eq, and } from "drizzle-orm";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import stripe from "#private/lib/stripe";
|
import stripe from "#private/lib/stripe";
|
||||||
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
|
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
|
||||||
import { AudienceIds, moveEmailToAudience } from "#private/lib/resend";
|
|
||||||
import { getSubType } from "./getSubType";
|
import { getSubType } from "./getSubType";
|
||||||
import privateConfig from "#private/lib/config";
|
import privateConfig from "#private/lib/config";
|
||||||
import { getLicensePriceSet, LicenseId } from "@server/lib/billing/licenses";
|
import { getLicensePriceSet, LicenseId } from "@server/lib/billing/licenses";
|
||||||
@@ -172,7 +171,7 @@ export async function handleSubscriptionCreated(
|
|||||||
const email = orgUserRes.user.email;
|
const email = orgUserRes.user.email;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
moveEmailToAudience(email, AudienceIds.Subscribed);
|
// TODO: update user in Sendy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type === "license") {
|
} else if (type === "license") {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
|
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
|
||||||
import { AudienceIds, moveEmailToAudience } from "#private/lib/resend";
|
|
||||||
import { getSubType } from "./getSubType";
|
import { getSubType } from "./getSubType";
|
||||||
import stripe from "#private/lib/stripe";
|
import stripe from "#private/lib/stripe";
|
||||||
import privateConfig from "#private/lib/config";
|
import privateConfig from "#private/lib/config";
|
||||||
@@ -109,7 +108,7 @@ export async function handleSubscriptionDeleted(
|
|||||||
const email = orgUserRes.user.email;
|
const email = orgUserRes.user.email;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
moveEmailToAudience(email, AudienceIds.Churned);
|
// TODO: update user in Sendy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type === "license") {
|
} else if (type === "license") {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { checkValidInvite } from "@server/auth/checkValidInvite";
|
|||||||
import { passwordSchema } from "@server/auth/passwordSchema";
|
import { passwordSchema } from "@server/auth/passwordSchema";
|
||||||
import { UserType } from "@server/types/UserTypes";
|
import { UserType } from "@server/types/UserTypes";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import resend, { AudienceIds, moveEmailToAudience } from "#dynamic/lib/resend";
|
|
||||||
|
|
||||||
export const signupBodySchema = z.object({
|
export const signupBodySchema = z.object({
|
||||||
email: z.email().toLowerCase(),
|
email: z.email().toLowerCase(),
|
||||||
@@ -213,7 +212,7 @@ export async function signup(
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
`User ${email} opted in to marketing emails during signup.`
|
`User ${email} opted in to marketing emails during signup.`
|
||||||
);
|
);
|
||||||
moveEmailToAudience(email, AudienceIds.SignUps);
|
// TODO: update user in Sendy
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.getRawConfig().flags?.require_email_verification) {
|
if (config.getRawConfig().flags?.require_email_verification) {
|
||||||
|
|||||||
Reference in New Issue
Block a user