(params: {
scope: 'user' | 'organization'
planName: string
percentBefore: number
percentAfter: number
userId?: string
userEmail?: string
userName?: string
organizationId?: string
/** Workspace the usage occurred in, used to build a live upgrade/billing link. */
workspaceId?: string
currentUsageAfter: number
limit: number
})
| 821 | * - For organization plans, emails owners/admins who have notifications enabled. |
| 822 | */ |
| 823 | export async function maybeSendUsageThresholdEmail(params: { |
| 824 | scope: 'user' | 'organization' |
| 825 | planName: string |
| 826 | percentBefore: number |
| 827 | percentAfter: number |
| 828 | userId?: string |
| 829 | userEmail?: string |
| 830 | userName?: string |
| 831 | organizationId?: string |
| 832 | /** Workspace the usage occurred in, used to build a live upgrade/billing link. */ |
| 833 | workspaceId?: string |
| 834 | currentUsageAfter: number |
| 835 | limit: number |
| 836 | }): Promise<void> { |
| 837 | try { |
| 838 | if (!isBillingEnabled) return |
| 839 | if (params.limit <= 0 || params.currentUsageAfter <= 0) return |
| 840 | |
| 841 | const baseUrl = getBaseUrl() |
| 842 | const isFreeUser = params.planName === 'Free' |
| 843 | |
| 844 | const upgradeCreditsLink = params.workspaceId |
| 845 | ? `${baseUrl}${buildUpgradeHref(params.workspaceId, 'credits')}` |
| 846 | : `${baseUrl}/workspace` |
| 847 | const billingSettingsLink = params.workspaceId |
| 848 | ? `${baseUrl}/workspace/${params.workspaceId}/settings/billing` |
| 849 | : `${baseUrl}/workspace` |
| 850 | |
| 851 | // Check for 80% threshold crossing — used for paid users (budget warning) and free users (upgrade nudge) |
| 852 | const crosses80 = params.percentBefore < 80 && params.percentAfter >= 80 |
| 853 | // Check for 100% threshold (free users only — credits exhausted) |
| 854 | const crosses100 = params.percentBefore < 100 && params.percentAfter >= 100 |
| 855 | |
| 856 | // Skip if no thresholds crossed |
| 857 | if (!crosses80 && !crosses100) return |
| 858 | |
| 859 | // For 80% threshold email (paid users only) |
| 860 | if (crosses80 && !isFreeUser) { |
| 861 | const ctaLink = billingSettingsLink |
| 862 | const sendTo = async (email: string, name?: string) => { |
| 863 | const prefs = await getEmailPreferences(email) |
| 864 | if (prefs?.unsubscribeAll || prefs?.unsubscribeNotifications) return |
| 865 | |
| 866 | const html = await renderUsageThresholdEmail({ |
| 867 | userName: name, |
| 868 | planName: params.planName, |
| 869 | percentUsed: Math.min(100, Math.round(params.percentAfter)), |
| 870 | currentUsage: params.currentUsageAfter, |
| 871 | limit: params.limit, |
| 872 | ctaLink, |
| 873 | }) |
| 874 | |
| 875 | await sendEmail({ |
| 876 | to: email, |
| 877 | subject: getEmailSubject('usage-threshold'), |
| 878 | html, |
| 879 | emailType: 'notifications', |
| 880 | }) |
no test coverage detected