mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(types): add email types for sending emails (#3020)
This commit is contained in:
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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(),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user