@ -29,13 +29,57 @@ struct ResendEmailRequest<'a> {
/// without network access to the Resend API.
pub const TEST_API_KEY : & str = "re_test_bypass_no_send" ;
/// Send an email via the Resend API.
///
/// Shared implementation used by both magic link and synthesis emails.
/// When `api_key` equals [`TEST_API_KEY`], the external call is skipped
/// and the function returns success immediately (used in integration tests).
async fn send_via_resend (
client : & reqwest ::Client ,
api_key : & str ,
request_body : & ResendEmailRequest < ' _ > ,
context : & str ,
) -> Result < ( ) , AppError > {
if api_key = = TEST_API_KEY {
tracing ::debug ! ( to = ? request_body . to , "Email send bypassed (test mode)" ) ;
return Ok ( ( ) ) ;
}
let response = client
. post ( RESEND_API_URL )
. header ( "Authorization" , format! ( "Bearer {}" , api_key ) )
. json ( request_body )
. send ( )
. await
. map_err ( | e | {
tracing ::error ! ( "Failed to send {} via Resend: {:?}" , context , e ) ;
AppError ::Internal ( anyhow ::anyhow ! ( "Failed to send {}" , context ) )
} ) ? ;
if ! response . status ( ) . is_success ( ) {
let status = response . status ( ) ;
let body = response
. text ( )
. await
. unwrap_or_else ( | _ | "unknown" . to_string ( ) ) ;
tracing ::error ! (
status = % status ,
body = % body ,
"Resend API returned error for {}" , context
) ;
return Err ( AppError ::Internal ( anyhow ::anyhow ! (
"Email service returned status {}" , status
) ) ) ;
}
tracing ::info ! ( to = ? request_body . to , "{} sent successfully" , context ) ;
Ok ( ( ) )
}
/// Send a magic link email to the given address.
///
/// The email contains a link that the user clicks to authenticate.
/// The link points to `GET /api/v1/auth/verify?token={raw_token}`.
///
/// When `api_key` equals [`TEST_API_KEY`], the external call is skipped
/// and the function returns success immediately (used in integration tests).
pub async fn send_magic_link (
client : & reqwest ::Client ,
api_key : & str ,
@ -44,12 +88,6 @@ pub async fn send_magic_link(
app_url : & str ,
raw_token : & str ,
) -> Result < ( ) , AppError > {
// Bypass for integration tests — no external HTTP call
if api_key = = TEST_API_KEY {
tracing ::debug ! ( to = to , "Email send bypassed (test mode)" ) ;
return Ok ( ( ) ) ;
}
let verify_url = format! ( "{}/api/v1/auth/verify?token={}" , app_url , raw_token ) ;
let html = format! (
@ -82,35 +120,7 @@ pub async fn send_magic_link(
text : None ,
} ;
let response = client
. post ( RESEND_API_URL )
. header ( "Authorization" , format! ( "Bearer {}" , api_key ) )
. json ( & request_body )
. send ( )
. await
. map_err ( | e | {
tracing ::error ! ( "Failed to send email via Resend: {:?}" , e ) ;
AppError ::Internal ( anyhow ::anyhow ! ( "Failed to send email" ) )
} ) ? ;
if ! response . status ( ) . is_success ( ) {
let status = response . status ( ) ;
let body = response
. text ( )
. await
. unwrap_or_else ( | _ | "unknown" . to_string ( ) ) ;
tracing ::error ! (
status = % status ,
body = % body ,
"Resend API returned error"
) ;
return Err ( AppError ::Internal ( anyhow ::anyhow ! (
"Email service returned status {}" , status
) ) ) ;
}
tracing ::info ! ( to = to , "Magic link email sent successfully" ) ;
Ok ( ( ) )
send_via_resend ( client , api_key , & request_body , "magic link email" ) . await
}
/// Escape special HTML characters to prevent XSS in email templates.
@ -227,12 +237,6 @@ pub async fn send_synthesis_email(
date : & str ,
sections : & [ NewsSection ] ,
) -> Result < ( ) , AppError > {
// Bypass for integration tests
if api_key = = TEST_API_KEY {
tracing ::debug ! ( to = to , "Synthesis email send bypassed (test mode)" ) ;
return Ok ( ( ) ) ;
}
let html = build_synthesis_html ( week , date , sections ) ;
let text = build_synthesis_text ( week , date , sections ) ;
let subject = format! ( "Synthese de la Semaine {} - AI Weekly Synth" , week ) ;
@ -245,35 +249,7 @@ pub async fn send_synthesis_email(
text : Some ( & text ) ,
} ;
let response = client
. post ( RESEND_API_URL )
. header ( "Authorization" , format! ( "Bearer {}" , api_key ) )
. json ( & request_body )
. send ( )
. await
. map_err ( | e | {
tracing ::error ! ( "Failed to send synthesis email via Resend: {:?}" , e ) ;
AppError ::Internal ( anyhow ::anyhow ! ( "Failed to send synthesis email" ) )
} ) ? ;
if ! response . status ( ) . is_success ( ) {
let status = response . status ( ) ;
let body = response
. text ( )
. await
. unwrap_or_else ( | _ | "unknown" . to_string ( ) ) ;
tracing ::error ! (
status = % status ,
body = % body ,
"Resend API returned error for synthesis email"
) ;
return Err ( AppError ::Internal ( anyhow ::anyhow ! (
"Email service returned status {}" , status
) ) ) ;
}
tracing ::info ! ( to = to , week = week , "Synthesis email sent successfully" ) ;
Ok ( ( ) )
send_via_resend ( client , api_key , & request_body , "synthesis email" ) . await
}
#[ cfg(test) ]