@@ -3,10 +3,14 @@ use std::sync::Mutex;
33
44use crate :: util:: errors:: { server_error, AppResult } ;
55
6+ use crate :: config;
7+ use crate :: middleware:: log_request:: add_custom_metadata;
8+ use crate :: Env ;
69use lettre:: transport:: file:: FileTransport ;
710use lettre:: transport:: smtp:: authentication:: { Credentials , Mechanism } ;
811use lettre:: transport:: smtp:: SmtpTransport ;
912use lettre:: { Message , Transport } ;
13+ use rand:: distributions:: { Alphanumeric , DistString } ;
1014
1115#[ derive( Debug ) ]
1216pub struct Emails {
@@ -16,7 +20,7 @@ pub struct Emails {
1620impl Emails {
1721 /// Create a new instance detecting the backend from the environment. This will either connect
1822 /// to a SMTP server or store the emails on the local filesystem.
19- pub fn from_environment ( ) -> Self {
23+ pub fn from_environment ( config : & config :: Server ) -> Self {
2024 let backend = match (
2125 dotenv:: var ( "MAILGUN_SMTP_LOGIN" ) ,
2226 dotenv:: var ( "MAILGUN_SMTP_PASSWORD" ) ,
@@ -32,6 +36,10 @@ impl Emails {
3236 } ,
3337 } ;
3438
39+ if config. base . env == Env :: Production && !matches ! ( backend, EmailBackend :: Smtp { .. } ) {
40+ panic ! ( "only the smtp backend is allowed in production" ) ;
41+ }
42+
3543 Self { backend }
3644 }
3745
@@ -94,7 +102,21 @@ or go to https://{domain}/me/pending-invites to manage all of your crate ownersh
94102 }
95103
96104 fn send ( & self , recipient : & str , subject : & str , body : & str ) -> AppResult < ( ) > {
105+ // The message ID is normally generated by the SMTP server, but if we let it generate the
106+ // ID there will be no way for the crates.io application to know the ID of the message it
107+ // just sent, as it's not included in the SMTP response.
108+ //
109+ // Our support staff needs to know the message ID to be able to find misdelivered emails.
110+ // Because of that we're generating a random message ID, hoping the SMTP server doesn't
111+ // replace it when it relays the message.
112+ let message_id = format ! (
113+ "<{}@{}>" ,
114+ Alphanumeric . sample_string( & mut rand:: thread_rng( ) , 32 ) ,
115+ crate :: config:: domain_name( ) ,
116+ ) ;
117+
97118 let email = Message :: builder ( )
119+ . message_id ( Some ( message_id. clone ( ) ) )
98120 . to ( recipient. parse ( ) ?)
99121 . from ( self . sender_address ( ) . parse ( ) ?)
100122 . subject ( subject)
@@ -106,23 +128,42 @@ or go to https://{domain}/me/pending-invites to manage all of your crate ownersh
106128 login,
107129 password,
108130 } => {
109- SmtpTransport :: relay ( server) ?
110- . credentials ( Credentials :: new ( login. clone ( ) , password. clone ( ) ) )
111- . authentication ( vec ! [ Mechanism :: Plain ] )
112- . build ( )
113- . send ( & email)
114- . map_err ( |_| server_error ( "Error in sending email" ) ) ?;
131+ add_custom_metadata ( "email_backend" , "smtp" ) ;
132+
133+ SmtpTransport :: relay ( server)
134+ . and_then ( |transport| {
135+ transport
136+ . credentials ( Credentials :: new ( login. clone ( ) , password. clone ( ) ) )
137+ . authentication ( vec ! [ Mechanism :: Plain ] )
138+ . build ( )
139+ . send ( & email)
140+ } )
141+ . map_err ( |e| {
142+ add_custom_metadata ( "email_error" , e) ;
143+ server_error ( "Failed to send the email" )
144+ } ) ?;
145+
146+ add_custom_metadata ( "email_id" , message_id) ;
115147 }
116148 EmailBackend :: FileSystem { path } => {
117- FileTransport :: new ( & path)
118- . send ( & email)
119- . map_err ( |_| server_error ( "Email file could not be generated" ) ) ?;
149+ add_custom_metadata ( "email_backend" , "fs" ) ;
150+
151+ let id = FileTransport :: new ( & path) . send ( & email) . map_err ( |err| {
152+ add_custom_metadata ( "email_error" , err) ;
153+ server_error ( "Email file could not be generated" )
154+ } ) ?;
155+
156+ add_custom_metadata ( "email_path" , path. join ( format ! ( "{id}.eml" ) ) . display ( ) ) ;
157+ }
158+ EmailBackend :: Memory { mails } => {
159+ add_custom_metadata ( "email_backend" , "memory" ) ;
160+
161+ mails. lock ( ) . unwrap ( ) . push ( StoredEmail {
162+ to : recipient. into ( ) ,
163+ subject : subject. into ( ) ,
164+ body : body. into ( ) ,
165+ } ) ;
120166 }
121- EmailBackend :: Memory { mails } => mails. lock ( ) . unwrap ( ) . push ( StoredEmail {
122- to : recipient. into ( ) ,
123- subject : subject. into ( ) ,
124- body : body. into ( ) ,
125- } ) ,
126167 }
127168
128169 Ok ( ( ) )
0 commit comments