@@ -3,6 +3,10 @@ import { EmailMessage, EmailProvider, EmailProviderType, EmailSendResult } from
33const DEFAULT_FROM_EMAIL = process . env . EMAIL_FROM_ADDRESS ?? '[email protected] ' ; 44const DEFAULT_FROM_NAME = process . env . EMAIL_FROM_NAME ?? 'TeachLink' ;
55
6+ const MAX_RETRIES = 3 ;
7+ const BASE_BACKOFF_MS = 500 ;
8+ const MAX_BACKOFF_MS = 5000 ;
9+
610function asArray < T > ( value : T | T [ ] ) : T [ ] {
711 return Array . isArray ( value ) ? value : [ value ] ;
812}
@@ -11,6 +15,22 @@ function resolveFrom(message: EmailMessage) {
1115 return message . from ?? { email : DEFAULT_FROM_EMAIL , name : DEFAULT_FROM_NAME } ;
1216}
1317
18+ function wait ( ms : number ) : Promise < void > {
19+ return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
20+ }
21+
22+ function isRetryableResult ( result : EmailSendResult ) : boolean {
23+ if ( result . success ) return false ;
24+ if ( result . error ?. includes ( 'SENDGRID_API_KEY is not configured' ) ) return false ;
25+ const match = / S e n d G r i d e r r o r ( \\ d + ) / . exec ( result . error ?? '' ) ;
26+ if ( match ) {
27+ const status = parseInt ( match [ 1 ] ) ;
28+ return status === 408 || status === 429 || status >= 500 ;
29+ }
30+ // Network errors and other transient exceptions should be retried.
31+ return true ;
32+ }
33+
1434class SendGridProvider implements EmailProvider {
1535 readonly type : EmailProviderType = 'sendgrid' ;
1636
@@ -20,6 +40,30 @@ class SendGridProvider implements EmailProvider {
2040 return { success : false , provider : this . type , error : 'SENDGRID_API_KEY is not configured' } ;
2141 }
2242
43+ let lastResult : EmailSendResult = {
44+ success : false ,
45+ provider : this . type ,
46+ error : 'Email send failed after retries' ,
47+ } ;
48+ let delay = BASE_BACKOFF_MS ;
49+
50+ for ( let attempt = 0 ; attempt <= MAX_RETRIES ; attempt ++ ) {
51+ lastResult = await this . doSend ( message , apiKey ) ;
52+
53+ if ( lastResult . success || ! isRetryableResult ( lastResult ) ) {
54+ return lastResult ;
55+ }
56+
57+ if ( attempt < MAX_RETRIES ) {
58+ await wait ( delay ) ;
59+ delay = Math . min ( delay * 2 , MAX_BACKOFF_MS ) ;
60+ }
61+ }
62+
63+ return lastResult ;
64+ }
65+
66+ private async doSend ( message : EmailMessage , apiKey : string ) : Promise < EmailSendResult > {
2367 try {
2468 const response = await fetch ( 'https://api.sendgrid.com/v3/mail/send' , {
2569 method : 'POST' ,
0 commit comments