2024-06-05 13:20:26 -07:00
import { supabase_service } from "../supabase" ;
import { withAuth } from "../../lib/withAuth" ;
import { Resend } from "resend" ;
import { NotificationType } from "../../types" ;
2024-11-07 20:57:33 +01:00
import { logger } from "../../../src/lib/logger" ;
2024-10-15 17:28:28 -03:00
import { sendSlackWebhook } from "../alerts/slack" ;
import { getNotificationString } from "./notification_string" ;
import { AuthCreditUsageChunk } from "../../controllers/v1/types" ;
2024-11-12 20:09:01 -05:00
import { redlock } from "../redlock" ;
2024-06-05 13:20:26 -07:00
const emailTemplates : Record <
NotificationType ,
{ subject : string ; html : string }
> = {
[ NotificationType . APPROACHING_LIMIT ] : {
subject : "You've used 80% of your credit limit - Firecrawl" ,
2024-12-11 19:51:08 -03:00
html : "Hey there,<br/><p>You are approaching your credit limit for this billing period. Your usage right now is around 80% of your total credit limit. Consider upgrading your plan to avoid hitting the limit. Check out our <a href='https://firecrawl.dev/pricing'>pricing page</a> for more info.</p><br/>Thanks,<br/>Firecrawl Team<br/>" ,
2024-06-05 13:20:26 -07:00
} ,
[ NotificationType . LIMIT_REACHED ] : {
2024-06-06 11:23:10 -07:00
subject :
"Credit Limit Reached! Take action now to resume usage - Firecrawl" ,
2024-12-11 19:51:08 -03:00
html : "Hey there,<br/><p>You have reached your credit limit for this billing period. To resume usage, please upgrade your plan. Check out our <a href='https://firecrawl.dev/pricing'>pricing page</a> for more info.</p><br/>Thanks,<br/>Firecrawl Team<br/>" ,
2024-06-05 13:20:26 -07:00
} ,
[ NotificationType . RATE_LIMIT_REACHED ] : {
subject : "Rate Limit Reached - Firecrawl" ,
2024-12-11 19:51:08 -03:00
html : "Hey there,<br/><p>You've hit one of the Firecrawl endpoint's rate limit! Take a breather and try again in a few moments. If you need higher rate limits, consider upgrading your plan. Check out our <a href='https://firecrawl.dev/pricing'>pricing page</a> for more info.</p><p>If you have any questions, feel free to reach out to us at <a href='mailto:help@firecrawl.com'>help@firecrawl.com</a></p><br/>Thanks,<br/>Firecrawl Team<br/><br/>Ps. this email is only sent once every 7 days if you reach a rate limit." ,
2024-06-05 13:20:26 -07:00
} ,
2024-10-22 19:47:23 -03:00
[ NotificationType . AUTO_RECHARGE_SUCCESS ] : {
subject : "Auto recharge successful - Firecrawl" ,
2024-12-11 19:51:08 -03:00
html : "Hey there,<br/><p>Your account was successfully recharged with 1000 credits because your remaining credits were below the threshold. Consider upgrading your plan at <a href='https://firecrawl.dev/pricing'>firecrawl.dev/pricing</a> to avoid hitting the limit.</p><br/>Thanks,<br/>Firecrawl Team<br/>" ,
2024-10-22 19:47:23 -03:00
} ,
[ NotificationType . AUTO_RECHARGE_FAILED ] : {
subject : "Auto recharge failed - Firecrawl" ,
2024-12-11 19:51:08 -03:00
html : "Hey there,<br/><p>Your auto recharge failed. Please try again manually. If the issue persists, please reach out to us at <a href='mailto:help@firecrawl.com'>help@firecrawl.com</a></p><br/>Thanks,<br/>Firecrawl Team<br/>" ,
} ,
2024-06-05 13:20:26 -07:00
} ;
export async function sendNotification (
team_id : string ,
notificationType : NotificationType ,
2024-11-07 20:57:33 +01:00
startDateString : string | null ,
endDateString : string | null ,
2024-10-22 19:47:23 -03:00
chunk : AuthCreditUsageChunk ,
2024-12-11 19:51:08 -03:00
bypassRecentChecks : boolean = false ,
2024-06-05 13:20:26 -07:00
) {
2024-11-07 20:57:33 +01:00
return withAuth ( sendNotificationInternal , undefined ) (
2024-06-05 13:20:26 -07:00
team_id ,
notificationType ,
startDateString ,
2024-10-15 17:28:28 -03:00
endDateString ,
2024-10-22 19:47:23 -03:00
chunk ,
2024-12-11 19:51:08 -03:00
bypassRecentChecks ,
2024-06-05 13:20:26 -07:00
) ;
}
2024-10-22 19:47:23 -03:00
export async function sendEmailNotification (
2024-06-05 13:20:26 -07:00
email : string ,
2024-12-11 19:51:08 -03:00
notificationType : NotificationType ,
2024-06-05 13:20:26 -07:00
) {
2024-06-06 11:23:10 -07:00
const resend = new Resend ( process . env . RESEND_API_KEY ) ;
2024-06-05 13:53:31 -07:00
2024-06-05 13:20:26 -07:00
try {
const { data , error } = await resend . emails . send ( {
from : "Firecrawl <firecrawl@getmendableai.com>" ,
to : [ email ] ,
2024-11-13 20:27:20 -05:00
reply_to : "help@firecrawl.com" ,
2024-06-05 13:20:26 -07:00
subject : emailTemplates [ notificationType ] . subject ,
2024-12-11 19:51:08 -03:00
html : emailTemplates [ notificationType ] . html ,
2024-06-05 13:20:26 -07:00
} ) ;
if ( error ) {
2024-11-07 20:57:33 +01:00
logger . debug ( ` Error sending email: ${ error } ` ) ;
2024-06-05 13:20:26 -07:00
return { success : false } ;
}
} catch ( error ) {
2024-11-07 20:57:33 +01:00
logger . debug ( ` Error sending email (2): ${ error } ` ) ;
2024-06-05 13:20:26 -07:00
return { success : false } ;
}
}
export async function sendNotificationInternal (
team_id : string ,
notificationType : NotificationType ,
2024-11-07 20:57:33 +01:00
startDateString : string | null ,
endDateString : string | null ,
2024-10-22 19:47:23 -03:00
chunk : AuthCreditUsageChunk ,
2024-12-11 19:51:08 -03:00
bypassRecentChecks : boolean = false ,
2024-06-05 13:20:26 -07:00
) : Promise < { success : boolean } > {
2025-01-25 19:02:32 +01:00
if ( team_id === "preview" || team_id . startsWith ( "preview_" ) ) {
2024-06-06 11:23:10 -07:00
return { success : true } ;
}
2024-12-11 19:46:11 -03:00
return await redlock . using (
[ ` notification-lock: ${ team_id } : ${ notificationType } ` ] ,
5000 ,
async ( ) = > {
if ( ! bypassRecentChecks ) {
const fifteenDaysAgo = new Date ( ) ;
fifteenDaysAgo . setDate ( fifteenDaysAgo . getDate ( ) - 15 ) ;
const { data , error } = await supabase_service
. from ( "user_notifications" )
. select ( "*" )
. eq ( "team_id" , team_id )
. eq ( "notification_type" , notificationType )
. gte ( "sent_date" , fifteenDaysAgo . toISOString ( ) ) ;
if ( error ) {
logger . debug ( ` Error fetching notifications: ${ error } ` ) ;
return { success : false } ;
}
if ( data . length !== 0 ) {
return { success : false } ;
}
// TODO: observation: Free credits people are not receiving notifications
const { data : recentData , error : recentError } = await supabase_service
. from ( "user_notifications" )
. select ( "*" )
. eq ( "team_id" , team_id )
. eq ( "notification_type" , notificationType )
. gte ( "sent_date" , startDateString )
. lte ( "sent_date" , endDateString ) ;
if ( recentError ) {
logger . debug (
2024-12-11 19:51:08 -03:00
` Error fetching recent notifications: ${ recentError . message } ` ,
2024-12-11 19:46:11 -03:00
) ;
return { success : false } ;
}
if ( recentData . length !== 0 ) {
return { success : false } ;
}
}
console . log (
2024-12-11 19:51:08 -03:00
` Sending notification for team_id: ${ team_id } and notificationType: ${ notificationType } ` ,
2024-12-11 19:46:11 -03:00
) ;
// get the emails from the user with the team_id
const { data : emails , error : emailsError } = await supabase_service
. from ( "users" )
. select ( "email" )
. eq ( "team_id" , team_id ) ;
if ( emailsError ) {
logger . debug ( ` Error fetching emails: ${ emailsError } ` ) ;
return { success : false } ;
}
for ( const email of emails ) {
await sendEmailNotification ( email . email , notificationType ) ;
}
const { error : insertError } = await supabase_service
. from ( "user_notifications" )
. insert ( [
{
team_id : team_id ,
notification_type : notificationType ,
sent_date : new Date ( ) . toISOString ( ) ,
2024-12-11 19:51:08 -03:00
timestamp : new Date ( ) . toISOString ( ) ,
} ,
2024-12-11 19:46:11 -03:00
] ) ;
if ( process . env . SLACK_ADMIN_WEBHOOK_URL && emails . length > 0 ) {
sendSlackWebhook (
` ${ getNotificationString ( notificationType ) } : Team ${ team_id } , with email ${ emails [ 0 ] . email } . Number of credits used: ${ chunk . adjusted_credits_used } | Number of credits in the plan: ${ chunk . price_credits } ` ,
false ,
2024-12-11 19:51:08 -03:00
process . env . SLACK_ADMIN_WEBHOOK_URL ,
2024-12-11 19:46:11 -03:00
) . catch ( ( error ) = > {
logger . debug ( ` Error sending slack notification: ${ error } ` ) ;
} ) ;
}
if ( insertError ) {
logger . debug ( ` Error inserting notification record: ${ insertError } ` ) ;
return { success : false } ;
}
return { success : true } ;
2024-12-11 19:51:08 -03:00
} ,
2024-12-11 19:46:11 -03:00
) ;
2024-06-05 13:20:26 -07:00
}