feat(types): add email types for sending emails (#3020)

This commit is contained in:
Narayan Bhat
2023-12-01 15:53:48 +05:30
committed by GitHub
parent 00b58a99c8
commit c4bd47eca9
3 changed files with 144 additions and 32 deletions

View File

@ -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

View File

@ -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 {{
} text-decoration: none !important;
.apple-email a { color: #448bff !important;
{ border: none !important;
text-decoration: none !important; }}
color: #448bff !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>
@ -140,8 +136,8 @@
align="center" align="center"
> >
<br /> <br />
Simply click on the link below, and you'll be granted instant access 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 to your Hyperswitch account. Note that this link expires in 24 hours
and can only be used once.<br /> and can only be used once.<br />
</td> </td>
</tr> </tr>

View File

@ -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(),
}) })