send free limit reached email for inactive plans

This commit is contained in:
KM Koushik
2025-10-26 11:05:51 +11:00
parent 7edff5b783
commit f525381fb9
+30 -31
View File
@@ -29,7 +29,7 @@ export class TeamService {
await redis.setex( await redis.setex(
TeamService.cacheKey(teamId), TeamService.cacheKey(teamId),
TEAM_CACHE_TTL_SECONDS, TEAM_CACHE_TTL_SECONDS,
JSON.stringify(team) JSON.stringify(team),
); );
return team; return team;
} }
@@ -54,7 +54,7 @@ export class TeamService {
static async createTeam( static async createTeam(
userId: number, userId: number,
name: string name: string,
): Promise<Team | undefined> { ): Promise<Team | undefined> {
const teams = await db.team.findMany({ const teams = await db.team.findMany({
where: { where: {
@@ -103,7 +103,7 @@ export class TeamService {
*/ */
static async updateTeam( static async updateTeam(
teamId: number, teamId: number,
data: Prisma.TeamUpdateInput data: Prisma.TeamUpdateInput,
): Promise<Team> { ): Promise<Team> {
const updated = await db.team.update({ where: { id: teamId }, data }); const updated = await db.team.update({ where: { id: teamId }, data });
await TeamService.refreshTeamCache(teamId); await TeamService.refreshTeamCache(teamId);
@@ -153,7 +153,7 @@ export class TeamService {
email: string, email: string,
role: "MEMBER" | "ADMIN", role: "MEMBER" | "ADMIN",
teamName: string, teamName: string,
sendEmail: boolean = true sendEmail: boolean = true,
): Promise<TeamInvite> { ): Promise<TeamInvite> {
if (!email) { if (!email) {
throw new TRPCError({ throw new TRPCError({
@@ -206,7 +206,7 @@ export class TeamService {
static async updateTeamUserRole( static async updateTeamUserRole(
teamId: number, teamId: number,
userId: string, userId: string,
role: "MEMBER" | "ADMIN" role: "MEMBER" | "ADMIN",
) { ) {
const teamUser = await db.teamUser.findFirst({ const teamUser = await db.teamUser.findFirst({
where: { where: {
@@ -257,7 +257,7 @@ export class TeamService {
teamId: number, teamId: number,
userId: string, userId: string,
requestorRole: string, requestorRole: string,
requestorId: number requestorId: number,
) { ) {
const teamUser = await db.teamUser.findFirst({ const teamUser = await db.teamUser.findFirst({
where: { where: {
@@ -361,16 +361,16 @@ export class TeamService {
static async maybeNotifyEmailLimitReached( static async maybeNotifyEmailLimitReached(
teamId: number, teamId: number,
limit: number, limit: number,
reason: LimitReason | undefined reason: LimitReason | undefined,
) { ) {
logger.info( logger.info(
{ teamId, limit, reason }, { teamId, limit, reason },
"[TeamService]: maybeNotifyEmailLimitReached called" "[TeamService]: maybeNotifyEmailLimitReached called",
); );
if (!reason) { if (!reason) {
logger.info( logger.info(
{ teamId }, { teamId },
"[TeamService]: Skipping notify — no reason provided" "[TeamService]: Skipping notify — no reason provided",
); );
return; return;
} }
@@ -383,7 +383,7 @@ export class TeamService {
) { ) {
logger.info( logger.info(
{ teamId, reason }, { teamId, reason },
"[TeamService]: Skipping notify — reason not eligible" "[TeamService]: Skipping notify — reason not eligible",
); );
return; return;
} }
@@ -394,7 +394,7 @@ export class TeamService {
if (alreadySent) { if (alreadySent) {
logger.info( logger.info(
{ teamId, cacheKey }, { teamId, cacheKey },
"[TeamService]: Skipping notify — cooldown active" "[TeamService]: Skipping notify — cooldown active",
); );
return; // within cooldown window return; // within cooldown window
} }
@@ -425,24 +425,24 @@ export class TeamService {
logger.info( logger.info(
{ teamId, recipientsCount: recipients.length, reason }, { teamId, recipientsCount: recipients.length, reason },
"[TeamService]: Sending limit reached notifications" "[TeamService]: Sending limit reached notifications",
); );
// Send individually to all team users // Send individually to all team users
try { try {
await Promise.all( await Promise.all(
recipients.map((to) => recipients.map((to) =>
sendMail(to, subject, text, html, "hey@usesend.com") sendMail(to, subject, text, html, "hey@usesend.com"),
) ),
); );
logger.info( logger.info(
{ teamId, recipientsCount: recipients.length }, { teamId, recipientsCount: recipients.length },
"[TeamService]: Limit reached notifications sent" "[TeamService]: Limit reached notifications sent",
); );
} catch (err) { } catch (err) {
logger.error( logger.error(
{ err, teamId }, { err, teamId },
"[TeamService]: Failed sending limit reached notifications" "[TeamService]: Failed sending limit reached notifications",
); );
throw err; throw err;
} }
@@ -451,7 +451,7 @@ export class TeamService {
await redis.setex(cacheKey, 6 * 60 * 60, "1"); await redis.setex(cacheKey, 6 * 60 * 60, "1");
logger.info( logger.info(
{ teamId, cacheKey }, { teamId, cacheKey },
"[TeamService]: Set limit reached notification cooldown" "[TeamService]: Set limit reached notification cooldown",
); );
} }
@@ -463,16 +463,16 @@ export class TeamService {
teamId: number, teamId: number,
used: number, used: number,
limit: number, limit: number,
reason: LimitReason | undefined reason: LimitReason | undefined,
) { ) {
logger.info( logger.info(
{ teamId, used, limit, reason }, { teamId, used, limit, reason },
"[TeamService]: sendWarningEmail called" "[TeamService]: sendWarningEmail called",
); );
if (!reason) { if (!reason) {
logger.info( logger.info(
{ teamId }, { teamId },
"[TeamService]: Skipping warning — no reason provided" "[TeamService]: Skipping warning — no reason provided",
); );
return; return;
} }
@@ -485,7 +485,7 @@ export class TeamService {
) { ) {
logger.info( logger.info(
{ teamId, reason }, { teamId, reason },
"[TeamService]: Skipping warning — reason not eligible" "[TeamService]: Skipping warning — reason not eligible",
); );
return; return;
} }
@@ -496,7 +496,7 @@ export class TeamService {
if (alreadySent) { if (alreadySent) {
logger.info( logger.info(
{ teamId, cacheKey }, { teamId, cacheKey },
"[TeamService]: Skipping warning — cooldown active" "[TeamService]: Skipping warning — cooldown active",
); );
return; // within cooldown window return; // within cooldown window
} }
@@ -537,23 +537,23 @@ export class TeamService {
logger.info( logger.info(
{ teamId, recipientsCount: recipients.length, reason }, { teamId, recipientsCount: recipients.length, reason },
"[TeamService]: Sending warning notifications" "[TeamService]: Sending warning notifications",
); );
try { try {
await Promise.all( await Promise.all(
recipients.map((to) => recipients.map((to) =>
sendMail(to, subject, text, html, "hey@usesend.com") sendMail(to, subject, text, html, "hey@usesend.com"),
) ),
); );
logger.info( logger.info(
{ teamId, recipientsCount: recipients.length }, { teamId, recipientsCount: recipients.length },
"[TeamService]: Warning notifications sent" "[TeamService]: Warning notifications sent",
); );
} catch (err) { } catch (err) {
logger.error( logger.error(
{ err, teamId }, { err, teamId },
"[TeamService]: Failed sending warning notifications" "[TeamService]: Failed sending warning notifications",
); );
throw err; throw err;
} }
@@ -562,19 +562,18 @@ export class TeamService {
await redis.setex(cacheKey, 6 * 60 * 60, "1"); await redis.setex(cacheKey, 6 * 60 * 60, "1");
logger.info( logger.info(
{ teamId, cacheKey }, { teamId, cacheKey },
"[TeamService]: Set warning notification cooldown" "[TeamService]: Set warning notification cooldown",
); );
} }
} }
async function getLimitReachedEmail( async function getLimitReachedEmail(
teamId: number, teamId: number,
limit: number, limit: number,
reason: LimitReason reason: LimitReason,
) { ) {
const team = await TeamService.getTeamCached(teamId); const team = await TeamService.getTeamCached(teamId);
const isPaidPlan = team.plan !== "FREE"; const isPaidPlan = team.isActive && team.plan !== "FREE";
const email = await renderUsageLimitReachedEmail({ const email = await renderUsageLimitReachedEmail({
teamName: team.name, teamName: team.name,
limit, limit,