From 0f6e45c8e5df56742f252f45612fd185326b9824 Mon Sep 17 00:00:00 2001 From: Narayan Bhat Date: Thu, 30 Nov 2023 14:57:08 +0530 Subject: [PATCH 1/2] feat: add other email types --- crates/router/src/core/user.rs | 3 +- .../src/services/email/assets/magic_link.html | 34 ++--- crates/router/src/services/email/types.rs | 141 ++++++++++++++++-- 3 files changed, 145 insertions(+), 33 deletions(-) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 1dc0e2e1a112..4f9dd3879400 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -76,9 +76,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 diff --git a/crates/router/src/services/email/assets/magic_link.html b/crates/router/src/services/email/assets/magic_link.html index 6439c83f227c..1bab65d6a5be 100644 --- a/crates/router/src/services/email/assets/magic_link.html +++ b/crates/router/src/services/email/assets/magic_link.html @@ -2,20 +2,16 @@ Login to Hyperswitch
- Welcome to Hyperswitch! -

Dear {user_name},

- Dear {user_name},

+ We are thrilled to welcome you into our community! @@ -140,8 +136,8 @@ align="center" >
- 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.
diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index 8650e1c27c22..a4a4681c6001 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -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 { 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, -} - -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, + 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 { 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, + pub subject: &'static str, +} + +#[async_trait::async_trait] +impl EmailData for ResetPassword { + async fn get_email_data(&self) -> CustomResult { + 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, + pub subject: &'static str, +} + +#[async_trait::async_trait] +impl EmailData for MagicLink { + async fn get_email_data(&self) -> CustomResult { + 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, + pub subject: &'static str, +} + +#[async_trait::async_trait] +impl EmailData for InviteUser { + async fn get_email_data(&self) -> CustomResult { + 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(), }) From bdc50ad46ab5b83f8314f2e18c2df8483e663ffe Mon Sep 17 00:00:00 2001 From: Narayan Bhat Date: Thu, 30 Nov 2023 18:30:29 +0530 Subject: [PATCH 2/2] chore: format html --- crates/router/src/services/email/assets/magic_link.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/router/src/services/email/assets/magic_link.html b/crates/router/src/services/email/assets/magic_link.html index 1bab65d6a5be..643b6e230633 100644 --- a/crates/router/src/services/email/assets/magic_link.html +++ b/crates/router/src/services/email/assets/magic_link.html @@ -100,10 +100,10 @@ " align="center" > - Welcome to Hyperswitch!

Dear {user_name},

- We are thrilled to welcome you into our community! + Welcome to Hyperswitch! +

Dear {user_name},

+ + We are thrilled to welcome you into our community!