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>

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