mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +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; | ||||
|  | ||||
|             let email_contents = email_types::WelcomeEmail { | ||||
|             let email_contents = email_types::VerifyEmail { | ||||
|                 recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?, | ||||
|                 settings: state.conf.clone(), | ||||
|                 subject: "Welcome to the Hyperswitch community!", | ||||
|             }; | ||||
|  | ||||
|             let send_email_result = state | ||||
|  | ||||
| @ -2,20 +2,16 @@ | ||||
| <title>Login to Hyperswitch</title> | ||||
| <body style="background-color: #ececec"> | ||||
|   <style> | ||||
|     .apple-footer a { | ||||
|        { | ||||
|         text-decoration: none !important; | ||||
|         color: #999 !important; | ||||
|         border: none !important; | ||||
|       } | ||||
|     } | ||||
|     .apple-email a { | ||||
|        { | ||||
|         text-decoration: none !important; | ||||
|         color: #448bff !important; | ||||
|         border: none !important; | ||||
|       } | ||||
|     } | ||||
|     .apple-footer a {{ | ||||
|       text-decoration: none !important; | ||||
|       color: #999 !important; | ||||
|       border: none !important; | ||||
|     }} | ||||
|     .apple-email a {{ | ||||
|       text-decoration: none !important; | ||||
|       color: #448bff !important; | ||||
|       border: none !important; | ||||
|     }} | ||||
|   </style> | ||||
|   <div | ||||
|     id="wrapper" | ||||
| @ -106,8 +102,8 @@ | ||||
|         > | ||||
|           Welcome to Hyperswitch! | ||||
|           <p style="font-size: 18px">Dear {user_name},</p> | ||||
|           <span style="font-size: 18px" | ||||
|             >We are thrilled to welcome you into our community! | ||||
|             <span style="font-size: 18px"> | ||||
|               We are thrilled to welcome you into our community! | ||||
|           </span> | ||||
|         </td> | ||||
|       </tr> | ||||
| @ -140,8 +136,8 @@ | ||||
|           align="center" | ||||
|         > | ||||
|           <br /> | ||||
|           Simply click on the link below, and you'll be granted instant access | ||||
|           to your Hyperswitch account. Note that this link expires in 24 hours | ||||
|           Simply click on the link below, and you'll be granted instant access  | ||||
|           to your Hyperswitch account. Note that this link expires in 24 hours  | ||||
|           and can only be used once.<br /> | ||||
|         </td> | ||||
|       </tr> | ||||
|  | ||||
| @ -5,10 +5,13 @@ use masking::ExposeInterface; | ||||
|  | ||||
| use crate::{configs, consts}; | ||||
| #[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 { | ||||
|     Verify { link: String }, | ||||
|     Reset { link: String, user_name: String }, | ||||
|     MagicLink { link: String, user_name: String }, | ||||
|     InviteUser { link: String, user_name: String }, | ||||
| } | ||||
|  | ||||
| pub mod html { | ||||
| @ -19,6 +22,27 @@ pub mod html { | ||||
|             EmailBody::Verify { 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 { | ||||
|     pub async fn new_token( | ||||
|         email: UserEmail, | ||||
|         email: domain::UserEmail, | ||||
|         settings: &configs::settings::Settings, | ||||
|     ) -> CustomResult<String, UserErrors> { | ||||
|         let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS); | ||||
| @ -44,35 +68,126 @@ impl EmailToken { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct WelcomeEmail { | ||||
|     pub recipient_email: UserEmail, | ||||
|     pub settings: std::sync::Arc<configs::settings::Settings>, | ||||
| } | ||||
|  | ||||
| pub fn get_email_verification_link( | ||||
| pub fn get_link_with_token( | ||||
|     base_url: impl std::fmt::Display, | ||||
|     token: impl std::fmt::Display, | ||||
|     action: impl std::fmt::Display, | ||||
| ) -> 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 | ||||
| #[async_trait::async_trait] | ||||
| impl EmailData for WelcomeEmail { | ||||
| impl EmailData for VerifyEmail { | ||||
|     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 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 { | ||||
|             link: verify_email_link, | ||||
|         }); | ||||
|         let subject = "Welcome to the Hyperswitch community!".to_string(); | ||||
|  | ||||
|         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), | ||||
|             recipient: self.recipient_email.clone().into_inner(), | ||||
|         }) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Narayan Bhat
					Narayan Bhat