Skip to content

Commit

Permalink
refactor(email): switch email send to backend (#1433)
Browse files Browse the repository at this point in the history
  • Loading branch information
wsxiaoys authored Feb 10, 2024
1 parent 4c7ca15 commit 3988be4
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 46 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ee/tabby-webserver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ bincode = "1.3.3"
chrono = { workspace = true, features = ["serde"] }
futures.workspace = true
hash-ids = "0.2.1"
hyper = { workspace = true, features=["client"]}
hyper = { workspace = true, features = ["client"] }
jsonwebtoken = "9.1.0"
juniper.workspace = true
juniper-axum = { path = "../../crates/juniper-axum" }
lazy_static.workspace = true
lettre = "0.11.3"
lettre = { version = "0.11.3", features = ["tokio1", "tokio1-native-tls"] }
mime_guess = "2.0.4"
pin-project = "1.1.3"
querystring = "1.1.0"
Expand Down
15 changes: 1 addition & 14 deletions ee/tabby-webserver/src/schema/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,11 @@ pub struct EmailSettingInput {
pub smtp_password: Option<String>,
}

#[derive(thiserror::Error, Debug)]
pub enum SendEmailError {
#[error("Email is not enabled")]
NotEnabled,

#[error(transparent)]
Other(#[from] anyhow::Error),
}

#[async_trait]
pub trait EmailService: Send + Sync {
async fn get_email_setting(&self) -> Result<Option<EmailSetting>>;
async fn update_email_setting(&self, input: EmailSettingInput) -> Result<()>;
async fn delete_email_setting(&self) -> Result<()>;

async fn send_invitation_email(
&self,
email: String,
code: String,
) -> Result<(), SendEmailError>;
async fn send_invitation_email(&self, email: String, code: String) -> Result<()>;
}
2 changes: 1 addition & 1 deletion ee/tabby-webserver/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ impl Mutation {
.send_invitation_email(email, invitation.code)
.await;
match email_sent {
Ok(_) | Err(email::SendEmailError::NotEnabled) => {}
Ok(_) => {}
Err(e) => {
warn!(
"Failed to send invitation email, please check your SMTP settings are correct: {e}"
Expand Down
68 changes: 39 additions & 29 deletions ee/tabby-webserver/src/service/email.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
use std::sync::Arc;

use anyhow::{anyhow, Result};
use async_trait::async_trait;
use lettre::{
message::{Mailbox, MessageBuilder},
transport::smtp::{
authentication::{Credentials, Mechanism},
client::{Tls, TlsParameters},
SmtpTransportBuilder,
AsyncSmtpTransportBuilder,
},
Address, SmtpTransport, Transport,
Address, AsyncSmtpTransport, AsyncTransport, Tokio1Executor, Transport,
};
use tabby_db::{DbConn, DbEnum};
use tokio::sync::RwLock;
use tracing::warn;

use crate::schema::{
email::{
AuthMethod, EmailService, EmailSetting, EmailSettingInput, Encryption, SendEmailError,
},
email::{AuthMethod, EmailService, EmailSetting, EmailSettingInput, Encryption},
setting::SettingService,
};

struct EmailServiceImpl {
db: DbConn,
smtp_server: RwLock<Option<SmtpTransport>>,
smtp_server: Arc<RwLock<Option<AsyncSmtpTransport<Tokio1Executor>>>>,
from: RwLock<String>,
}

Expand All @@ -38,17 +38,19 @@ fn make_smtp_builder(
host: &str,
port: u16,
encryption: Encryption,
) -> Result<SmtpTransportBuilder> {
) -> Result<AsyncSmtpTransportBuilder> {
let tls_parameters = TlsParameters::new(host.into())?;

let builder = match encryption {
Encryption::StartTls => SmtpTransport::builder_dangerous(host)
Encryption::StartTls => AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host)
.port(port)
.tls(Tls::Required(tls_parameters)),
Encryption::SslTls => SmtpTransport::builder_dangerous(host)
Encryption::SslTls => AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host)
.port(port)
.tls(Tls::Wrapper(tls_parameters)),
Encryption::None => SmtpTransport::builder_dangerous(host).port(port),
Encryption::None => {
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host).port(port)
}
};

Ok(builder)
Expand All @@ -62,6 +64,7 @@ impl EmailServiceImpl {
password: String,
host: &str,
port: i32,
from_address: &str,
encryption: Encryption,
auth_method: AuthMethod,
) -> Result<()> {
Expand All @@ -72,6 +75,7 @@ impl EmailServiceImpl {
.authentication(auth_mechanism(auth_method))
.build(),
);
*self.from.write().await = from_address.into();
Ok(())
}

Expand All @@ -80,26 +84,36 @@ impl EmailServiceImpl {
*self.smtp_server.write().await = None;
}

async fn send_mail(
async fn send_mail_in_background(
&self,
to: String,
subject: String,
message: String,
) -> Result<(), SendEmailError> {
let smtp_server = self.smtp_server.read().await;
let Some(smtp_server) = &*smtp_server else {
return Err(SendEmailError::NotEnabled);
};
let from = self.from.read().await;
let address_from = to_address(from.clone())?;
) -> Result<()> {
let smtp_server = self.smtp_server.clone();
let from = self.from.read().await.clone();
let address_from = to_address(from)?;
let address_to = to_address(to)?;
let msg = MessageBuilder::new()
.subject(subject)
.from(Mailbox::new(None, address_from))
.from(Mailbox::new(Some("Tabby Server".to_owned()), address_from))
.to(Mailbox::new(None, address_to))
.body(message)
.map_err(anyhow::Error::msg)?;
smtp_server.send(&msg).map_err(anyhow::Error::msg)?;

tokio::spawn(async move {
let Some(smtp_server) = &*(smtp_server.read().await) else {
// Not enabled.
return;
};
match smtp_server.send(msg).await.map_err(anyhow::Error::msg) {
Ok(_) => {}
Err(err) => {
warn!("Failed to send mail due to {}", err);
}
};
});

Ok(())
}
}
Expand All @@ -121,6 +135,7 @@ pub async fn new_email_service(db: DbConn) -> Result<impl EmailService> {
setting.smtp_password,
&setting.smtp_server,
setting.smtp_port as i32,
&setting.from_address,
encryption,
auth_method,
)
Expand Down Expand Up @@ -157,7 +172,6 @@ impl EmailService for EmailServiceImpl {
input.auth_method.as_enum_str().into(),
)
.await?;
*self.from.write().await = input.smtp_username.clone();
let smtp_password = match input.smtp_password {
Some(pass) => pass,
None => {
Expand All @@ -174,6 +188,7 @@ impl EmailService for EmailServiceImpl {
smtp_password.clone(),
&input.smtp_server,
input.smtp_port,
&input.from_address,
input.encryption,
input.auth_method,
)
Expand All @@ -188,18 +203,13 @@ impl EmailService for EmailServiceImpl {
Ok(())
}

async fn send_invitation_email(
&self,
email: String,
code: String,
) -> Result<(), SendEmailError> {
async fn send_invitation_email(&self, email: String, code: String) -> Result<()> {
let network_setting = self.db.read_network_setting().await?;
let external_url = network_setting.external_url;
self.send_mail(
self.send_mail_in_background(
email,
"You've been invited to join a Tabby workspace!".into(),
format!("Welcome to Tabby! You have been invited to join a Tabby instance, where you can tap into\
AI-driven code completions and chat assistants. Your invite code is {code}, go to {external_url}/auth/signup?invitationCode={code} to join!"),
format!("Welcome to Tabby! You have been invited to join a Tabby Server, where you can tap into AI-driven code completions and chat assistants.\n\nGo to {external_url}/auth/signup?invitationCode={code} to join!"),
).await
}
}
Expand Down

0 comments on commit 3988be4

Please sign in to comment.