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

View File

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

View File

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