From da79575db63231fd5d5e09546d557362a93b8dad Mon Sep 17 00:00:00 2001 From: isra el Date: Mon, 25 Aug 2025 12:14:12 +0300 Subject: [PATCH] chore(api): improve checkout reminder email --- api/src/billing/abandoned-checkout.service.ts | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/api/src/billing/abandoned-checkout.service.ts b/api/src/billing/abandoned-checkout.service.ts index b7e5e5d..4ac0724 100644 --- a/api/src/billing/abandoned-checkout.service.ts +++ b/api/src/billing/abandoned-checkout.service.ts @@ -13,7 +13,8 @@ import { BillingService } from './billing.service' interface EmailConfig { template: string subject: string - hoursAfterExpiry: number + minutesBeforeExpiry?: number // Send X minutes before expiry + minutesAfterExpiry?: number // Send X minutes after expiry emailType: | 'first_reminder' | 'second_reminder' @@ -30,9 +31,10 @@ export class AbandonedCheckoutService { { template: 'abandoned-checkout-10-minutes', subject: '⏰ Your textbee pro upgrade is waiting!', - hoursAfterExpiry: -0.167, // 10 minutes before expiry (-10/60 hours) + minutesBeforeExpiry: 15, emailType: 'first_reminder', }, + ] constructor( @@ -62,42 +64,41 @@ export class AbandonedCheckoutService { * Send reminder emails for a specific email configuration */ private async sendReminderEmails(emailConfig: EmailConfig) { + const now = new Date() let windowStart: Date, windowEnd: Date let query: any - if (emailConfig.hoursAfterExpiry < 0) { - // Before expiry: find sessions that will expire soon - const targetTime = new Date() - targetTime.setHours( - targetTime.getHours() + Math.abs(emailConfig.hoursAfterExpiry), - ) - - windowStart = new Date(targetTime.getTime() - 60 * 60 * 1000) // 1 hour window - windowEnd = new Date(targetTime.getTime() + 60 * 60 * 1000) + if (emailConfig.minutesBeforeExpiry !== undefined) { + // BEFORE EXPIRY: Find sessions that will expire in X minutes + const targetExpiryTime = new Date(now.getTime() + emailConfig.minutesBeforeExpiry * 60 * 1000) + + windowStart = new Date(targetExpiryTime.getTime() - 10 * 60 * 1000) + windowEnd = new Date(targetExpiryTime.getTime() + 10 * 60 * 1000) query = { expiresAt: { $gte: windowStart, $lte: windowEnd, - $gt: new Date(), // Only send to sessions that haven't expired yet }, - 'abandonedEmails.emailType': { $ne: emailConfig.emailType }, + 'abandonedEmails.emailType': { $ne: emailConfig.emailType }, // Don't send duplicate emails } - } else { - // After expiry: find sessions that expired the specified time ago - const targetTime = new Date() - targetTime.setHours(targetTime.getHours() - emailConfig.hoursAfterExpiry) - - windowStart = new Date(targetTime.getTime() - 60 * 60 * 1000) - windowEnd = new Date(targetTime.getTime() + 60 * 60 * 1000) + } else if (emailConfig.minutesAfterExpiry !== undefined) { + // AFTER EXPIRY: Find sessions that expired X minutes ago + const targetExpiryTime = new Date(now.getTime() - emailConfig.minutesAfterExpiry * 60 * 1000) + + windowStart = new Date(targetExpiryTime.getTime() - 10 * 60 * 1000) + windowEnd = new Date(targetExpiryTime.getTime() + 10 * 60 * 1000) query = { expiresAt: { $gte: windowStart, $lte: windowEnd, }, - 'abandonedEmails.emailType': { $ne: emailConfig.emailType }, + 'abandonedEmails.emailType': { $ne: emailConfig.emailType }, // Don't send duplicate emails } + } else { + this.logger.error(`Invalid email config: must specify either minutesBeforeExpiry or minutesAfterExpiry`) + return } const abandonedSessions = await this.checkoutSessionModel