@@ -33,6 +33,7 @@ const PUBLIC_CAMPAIGN_BASE_URL = resolveCampaignBaseUrlFromEnv((key) =>
3333 Deno . env . get ( key ) ,
3434) ;
3535const SMTP_USER = normalizeEmail ( Deno . env . get ( "SMTP_USER" ) || "" ) ;
36+ const LEADMINER_FRONTEND_HOST = Deno . env . get ( "LEADMINER_FRONTEND_HOST" ) || "" ;
3637const DEFAULT_SENDER_DAILY_LIMIT = 1000 ;
3738const MAX_SENDER_DAILY_LIMIT = 2000 ;
3839const PROCESSING_BATCH_SIZE = 300 ;
@@ -284,8 +285,13 @@ function extractErrorMessage(error: unknown): string {
284285
285286function toTextFromHtml ( html : string ) : string {
286287 return html
288+ . replace ( / < b r \s * \/ ? > / gi, "\n" )
289+ . replace ( / < \/ p > / gi, "\n\n" )
290+ . replace ( / < \/ d i v > / gi, "\n\n" )
291+ . replace ( / < \/ l i > / gi, "\n" )
287292 . replace ( / < [ ^ > ] * > / g, " " )
288- . replace ( / \s + / g, " " )
293+ . replace ( / \n { 3 , } / g, "\n\n" )
294+ . replace ( / [ \t ] + / g, " " )
289295 . trim ( ) ;
290296}
291297
@@ -577,6 +583,7 @@ async function resolveSenderOptions(authorization: string, userEmail: string) {
577583 await getUserMiningSources ( authorization ) ,
578584 ) ;
579585
586+ // Refresh expired OAuth tokens
580587 for ( let i = 0 ; i < sources . length ; i ++ ) {
581588 const source = sources [ i ] ;
582589 const credentialIssue = getSenderCredentialIssue ( source ) ;
@@ -1130,7 +1137,7 @@ app.post("/campaigns/preview", authMiddleware, async (c: Context) => {
11301137 ) ,
11311138 footerTextTemplate,
11321139 ownerEmail,
1133- unsubscribeUrl : buildUnsubscribeUrl ( crypto . randomUUID ( ) ) ,
1140+ unsubscribeUrl : buildUnsubscribeUrl ( "preview-unsubscribe" ) ,
11341141 senderName,
11351142 plainTextOnly,
11361143 } ) ;
@@ -1768,6 +1775,16 @@ app.get("/unsubscribe/:token", async (c: Context) => {
17681775 const token = c . req . param ( "token" ) ;
17691776 const supabaseAdmin = createSupabaseAdmin ( ) ;
17701777
1778+ if ( token === "preview-unsubscribe" ) {
1779+ return new Response ( null , {
1780+ status : 302 ,
1781+ headers : {
1782+ ...corsHeaders ,
1783+ Location : `${ LEADMINER_FRONTEND_HOST } /unsubscribe/success?preview=true` ,
1784+ } ,
1785+ } ) ;
1786+ }
1787+
17711788 const { data : recipient , error } = await supabaseAdmin
17721789 . schema ( "private" )
17731790 . from ( "email_campaign_recipients" )
@@ -1776,16 +1793,13 @@ app.get("/unsubscribe/:token", async (c: Context) => {
17761793 . single ( ) ;
17771794
17781795 if ( error || ! recipient ) {
1779- return new Response (
1780- "<html><body><h1>Invalid unsubscribe link</h1><p>This link is not valid anymore.</p></body></html>" ,
1781- {
1782- status : 404 ,
1783- headers : {
1784- ...corsHeaders ,
1785- "Content-Type" : "text/html; charset=utf-8" ,
1786- } ,
1796+ return new Response ( null , {
1797+ status : 302 ,
1798+ headers : {
1799+ ...corsHeaders ,
1800+ Location : `${ LEADMINER_FRONTEND_HOST } /unsubscribe/failure` ,
17871801 } ,
1788- ) ;
1802+ } ) ;
17891803 }
17901804
17911805 await supabaseAdmin
@@ -1812,12 +1826,11 @@ app.get("/unsubscribe/:token", async (c: Context) => {
18121826 event_type : "unsubscribe" ,
18131827 } ) ;
18141828
1815- const html =
1816- "<html><body><h1>You are unsubscribed</h1><p>You will no longer receive campaign emails from this sender.</p></body></html>" ;
1817- return new Response ( html , {
1829+ return new Response ( null , {
1830+ status : 302 ,
18181831 headers : {
18191832 ...corsHeaders ,
1820- "Content-Type" : "text/html; charset=utf-8" ,
1833+ Location : ` ${ LEADMINER_FRONTEND_HOST } /unsubscribe/success` ,
18211834 } ,
18221835 } ) ;
18231836} ) ;
0 commit comments