From c4bd47eca93a158c9daeeeb18afb1e735eea8c94 Mon Sep 17 00:00:00 2001
From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com>
Date: Fri, 1 Dec 2023 15:53:48 +0530
Subject: [PATCH] feat(types): add email types for sending emails (#3020)
---
crates/router/src/core/user.rs | 3 +-
.../src/services/email/assets/magic_link.html | 32 ++--
crates/router/src/services/email/types.rs | 141 ++++++++++++++++--
3 files changed, 144 insertions(+), 32 deletions(-)
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 8e7f6c27a7..7c50e0c763 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -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
diff --git a/crates/router/src/services/email/assets/magic_link.html b/crates/router/src/services/email/assets/magic_link.html
index 6439c83f22..643b6e2306 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},
-
We are thrilled to welcome you into our community!
+
+ 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 8650e1c27c..a4a4681c60 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(),
})