mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	feat(types): add email types for sending emails (#3020)
This commit is contained in:
		| @ -81,9 +81,10 @@ pub async fn connect_account( | |||||||
|  |  | ||||||
|             use crate::services::email::types as email_types; |             use crate::services::email::types as email_types; | ||||||
|  |  | ||||||
|             let email_contents = email_types::WelcomeEmail { |             let email_contents = email_types::VerifyEmail { | ||||||
|                 recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, |                 recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, | ||||||
|                 settings: state.conf.clone(), |                 settings: state.conf.clone(), | ||||||
|  |                 subject: "Welcome to the Hyperswitch community!", | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             let send_email_result = state |             let send_email_result = state | ||||||
|  | |||||||
| @ -2,20 +2,16 @@ | |||||||
| <title>Login to Hyperswitch</title> | <title>Login to Hyperswitch</title> | ||||||
| <body style="background-color: #ececec"> | <body style="background-color: #ececec"> | ||||||
|   <style> |   <style> | ||||||
|     .apple-footer a { |     .apple-footer a {{ | ||||||
|        { |  | ||||||
|       text-decoration: none !important; |       text-decoration: none !important; | ||||||
|       color: #999 !important; |       color: #999 !important; | ||||||
|       border: none !important; |       border: none !important; | ||||||
|       } |     }} | ||||||
|     } |     .apple-email a {{ | ||||||
|     .apple-email a { |  | ||||||
|        { |  | ||||||
|       text-decoration: none !important; |       text-decoration: none !important; | ||||||
|       color: #448bff !important; |       color: #448bff !important; | ||||||
|       border: none !important; |       border: none !important; | ||||||
|       } |     }} | ||||||
|     } |  | ||||||
|   </style> |   </style> | ||||||
|   <div |   <div | ||||||
|     id="wrapper" |     id="wrapper" | ||||||
| @ -106,8 +102,8 @@ | |||||||
|         > |         > | ||||||
|           Welcome to Hyperswitch! |           Welcome to Hyperswitch! | ||||||
|           <p style="font-size: 18px">Dear {user_name},</p> |           <p style="font-size: 18px">Dear {user_name},</p> | ||||||
|           <span style="font-size: 18px" |             <span style="font-size: 18px"> | ||||||
|             >We are thrilled to welcome you into our community! |               We are thrilled to welcome you into our community! | ||||||
|           </span> |           </span> | ||||||
|         </td> |         </td> | ||||||
|       </tr> |       </tr> | ||||||
|  | |||||||
| @ -5,10 +5,13 @@ use masking::ExposeInterface; | |||||||
|  |  | ||||||
| use crate::{configs, consts}; | use crate::{configs, consts}; | ||||||
| #[cfg(feature = "olap")] | #[cfg(feature = "olap")] | ||||||
| use crate::{core::errors::UserErrors, services::jwt, types::domain::UserEmail}; | use crate::{core::errors::UserErrors, services::jwt, types::domain}; | ||||||
|  |  | ||||||
| pub enum EmailBody { | pub enum EmailBody { | ||||||
|     Verify { link: String }, |     Verify { link: String }, | ||||||
|  |     Reset { link: String, user_name: String }, | ||||||
|  |     MagicLink { link: String, user_name: String }, | ||||||
|  |     InviteUser { link: String, user_name: String }, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub mod html { | pub mod html { | ||||||
| @ -19,6 +22,27 @@ pub mod html { | |||||||
|             EmailBody::Verify { link } => { |             EmailBody::Verify { link } => { | ||||||
|                 format!(include_str!("assets/verify.html"), link = link) |                 format!(include_str!("assets/verify.html"), link = link) | ||||||
|             } |             } | ||||||
|  |             EmailBody::Reset { link, user_name } => { | ||||||
|  |                 format!( | ||||||
|  |                     include_str!("assets/reset.html"), | ||||||
|  |                     link = link, | ||||||
|  |                     username = user_name | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             EmailBody::MagicLink { link, user_name } => { | ||||||
|  |                 format!( | ||||||
|  |                     include_str!("assets/magic_link.html"), | ||||||
|  |                     user_name = user_name, | ||||||
|  |                     link = link | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             EmailBody::InviteUser { link, user_name } => { | ||||||
|  |                 format!( | ||||||
|  |                     include_str!("assets/invite.html"), | ||||||
|  |                     username = user_name, | ||||||
|  |                     link = link | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -31,7 +55,7 @@ pub struct EmailToken { | |||||||
|  |  | ||||||
| impl EmailToken { | impl EmailToken { | ||||||
|     pub async fn new_token( |     pub async fn new_token( | ||||||
|         email: UserEmail, |         email: domain::UserEmail, | ||||||
|         settings: &configs::settings::Settings, |         settings: &configs::settings::Settings, | ||||||
|     ) -> CustomResult<String, UserErrors> { |     ) -> CustomResult<String, UserErrors> { | ||||||
|         let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS); |         let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS); | ||||||
| @ -44,35 +68,126 @@ impl EmailToken { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct WelcomeEmail { | pub fn get_link_with_token( | ||||||
|     pub recipient_email: UserEmail, |  | ||||||
|     pub settings: std::sync::Arc<configs::settings::Settings>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn get_email_verification_link( |  | ||||||
|     base_url: impl std::fmt::Display, |     base_url: impl std::fmt::Display, | ||||||
|     token: impl std::fmt::Display, |     token: impl std::fmt::Display, | ||||||
|  |     action: impl std::fmt::Display, | ||||||
| ) -> String { | ) -> String { | ||||||
|     format!("{base_url}/user/verify_email/?token={token}") |     format!("{base_url}/user/{action}/?token={token}") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct VerifyEmail { | ||||||
|  |     pub recipient_email: domain::UserEmail, | ||||||
|  |     pub settings: std::sync::Arc<configs::settings::Settings>, | ||||||
|  |     pub subject: &'static str, | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Currently only HTML is supported | /// Currently only HTML is supported | ||||||
| #[async_trait::async_trait] | #[async_trait::async_trait] | ||||||
| impl EmailData for WelcomeEmail { | impl EmailData for VerifyEmail { | ||||||
|     async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> { |     async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> { | ||||||
|         let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings) |         let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings) | ||||||
|             .await |             .await | ||||||
|             .change_context(EmailError::TokenGenerationFailure)?; |             .change_context(EmailError::TokenGenerationFailure)?; | ||||||
|  |  | ||||||
|         let verify_email_link = get_email_verification_link(&self.settings.server.base_url, token); |         let verify_email_link = | ||||||
|  |             get_link_with_token(&self.settings.server.base_url, token, "verify_email"); | ||||||
|  |  | ||||||
|         let body = html::get_html_body(EmailBody::Verify { |         let body = html::get_html_body(EmailBody::Verify { | ||||||
|             link: verify_email_link, |             link: verify_email_link, | ||||||
|         }); |         }); | ||||||
|         let subject = "Welcome to the Hyperswitch community!".to_string(); |  | ||||||
|  |  | ||||||
|         Ok(EmailContents { |         Ok(EmailContents { | ||||||
|             subject, |             subject: self.subject.to_string(), | ||||||
|  |             body: external_services::email::IntermediateString::new(body), | ||||||
|  |             recipient: self.recipient_email.clone().into_inner(), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct ResetPassword { | ||||||
|  |     pub recipient_email: domain::UserEmail, | ||||||
|  |     pub user_name: domain::UserName, | ||||||
|  |     pub settings: std::sync::Arc<configs::settings::Settings>, | ||||||
|  |     pub subject: &'static str, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait::async_trait] | ||||||
|  | impl EmailData for ResetPassword { | ||||||
|  |     async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> { | ||||||
|  |         let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings) | ||||||
|  |             .await | ||||||
|  |             .change_context(EmailError::TokenGenerationFailure)?; | ||||||
|  |  | ||||||
|  |         let reset_password_link = | ||||||
|  |             get_link_with_token(&self.settings.server.base_url, token, "set_password"); | ||||||
|  |  | ||||||
|  |         let body = html::get_html_body(EmailBody::Reset { | ||||||
|  |             link: reset_password_link, | ||||||
|  |             user_name: self.user_name.clone().get_secret().expose(), | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         Ok(EmailContents { | ||||||
|  |             subject: self.subject.to_string(), | ||||||
|  |             body: external_services::email::IntermediateString::new(body), | ||||||
|  |             recipient: self.recipient_email.clone().into_inner(), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct MagicLink { | ||||||
|  |     pub recipient_email: domain::UserEmail, | ||||||
|  |     pub user_name: domain::UserName, | ||||||
|  |     pub settings: std::sync::Arc<configs::settings::Settings>, | ||||||
|  |     pub subject: &'static str, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait::async_trait] | ||||||
|  | impl EmailData for MagicLink { | ||||||
|  |     async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> { | ||||||
|  |         let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings) | ||||||
|  |             .await | ||||||
|  |             .change_context(EmailError::TokenGenerationFailure)?; | ||||||
|  |  | ||||||
|  |         let magic_link_login = get_link_with_token(&self.settings.server.base_url, token, "login"); | ||||||
|  |  | ||||||
|  |         let body = html::get_html_body(EmailBody::MagicLink { | ||||||
|  |             link: magic_link_login, | ||||||
|  |             user_name: self.user_name.clone().get_secret().expose(), | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         Ok(EmailContents { | ||||||
|  |             subject: self.subject.to_string(), | ||||||
|  |             body: external_services::email::IntermediateString::new(body), | ||||||
|  |             recipient: self.recipient_email.clone().into_inner(), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct InviteUser { | ||||||
|  |     pub recipient_email: domain::UserEmail, | ||||||
|  |     pub user_name: domain::UserName, | ||||||
|  |     pub settings: std::sync::Arc<configs::settings::Settings>, | ||||||
|  |     pub subject: &'static str, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait::async_trait] | ||||||
|  | impl EmailData for InviteUser { | ||||||
|  |     async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> { | ||||||
|  |         let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings) | ||||||
|  |             .await | ||||||
|  |             .change_context(EmailError::TokenGenerationFailure)?; | ||||||
|  |  | ||||||
|  |         let invite_user_link = | ||||||
|  |             get_link_with_token(&self.settings.server.base_url, token, "set_password"); | ||||||
|  |  | ||||||
|  |         let body = html::get_html_body(EmailBody::MagicLink { | ||||||
|  |             link: invite_user_link, | ||||||
|  |             user_name: self.user_name.clone().get_secret().expose(), | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         Ok(EmailContents { | ||||||
|  |             subject: self.subject.to_string(), | ||||||
|             body: external_services::email::IntermediateString::new(body), |             body: external_services::email::IntermediateString::new(body), | ||||||
|             recipient: self.recipient_email.clone().into_inner(), |             recipient: self.recipient_email.clone().into_inner(), | ||||||
|         }) |         }) | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Narayan Bhat
					Narayan Bhat