From 2c78f1ad39e43889628aa327a5eddd75ee92770c Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Sun, 4 Aug 2024 17:02:37 -0700 Subject: [PATCH 1/4] chore: add email alerts --- packages/relayer/eml_templates/error.html | 3 +- .../relayer/eml_templates/error_alert.html | 417 ++++++++++++++++++ packages/relayer/src/config.rs | 6 + packages/relayer/src/lib.rs | 5 + packages/relayer/src/modules/claimer.rs | 2 +- packages/relayer/src/modules/mail.rs | 30 +- .../src/modules/web_server/rest_api.rs | 1 + packages/relayer/src/utils/strings.rs | 1 + packages/relayer/src/utils/utils.rs | 2 +- 9 files changed, 460 insertions(+), 7 deletions(-) create mode 100644 packages/relayer/eml_templates/error_alert.html diff --git a/packages/relayer/eml_templates/error.html b/packages/relayer/eml_templates/error.html index 07ce9ede..341724ff 100644 --- a/packages/relayer/eml_templates/error.html +++ b/packages/relayer/eml_templates/error.html @@ -166,8 +166,7 @@ margin-bottom: 15px; " > - Your email transaction failed due to the following error: - {{errorMsg}} + Your email transaction failed. Please try again later.

diff --git a/packages/relayer/eml_templates/error_alert.html b/packages/relayer/eml_templates/error_alert.html new file mode 100644 index 00000000..88ec9b1d --- /dev/null +++ b/packages/relayer/eml_templates/error_alert.html @@ -0,0 +1,417 @@ + + + + + + Email Wallet + + + Error in your Email Wallet + + + + + + + + + diff --git a/packages/relayer/src/config.rs b/packages/relayer/src/config.rs index a69938e9..61daf6ca 100644 --- a/packages/relayer/src/config.rs +++ b/packages/relayer/src/config.rs @@ -7,6 +7,7 @@ use dotenv::dotenv; #[derive(Clone)] pub struct RelayerConfig { pub smtp_server: String, + pub error_email_addresses: Vec, pub relayer_email_addr: String, pub db_path: String, pub web_server_address: String, @@ -54,6 +55,11 @@ impl RelayerConfig { Self { smtp_server: env::var(SMTP_SERVER_KEY).unwrap(), + error_email_addresses: env::var(ERROR_EMAIL_ADDRESSES_KEY) + .unwrap() + .split(',') + .map(|s| s.to_string()) + .collect(), relayer_email_addr: env::var(RELAYER_EMAIL_ADDR_KEY).unwrap(), db_path: env::var(DATABASE_PATH_KEY).unwrap(), web_server_address: env::var(WEB_SERVER_ADDRESS_KEY).unwrap(), diff --git a/packages/relayer/src/lib.rs b/packages/relayer/src/lib.rs index ee0afd5a..d0f32c95 100644 --- a/packages/relayer/src/lib.rs +++ b/packages/relayer/src/lib.rs @@ -57,6 +57,7 @@ pub static ONBOARDING_REPLY_MSG: OnceLock = OnceLock::new(); pub static RELAYER_EMAIL_ADDRESS: OnceLock = OnceLock::new(); pub static SAFE_API_ENDPOINT: OnceLock = OnceLock::new(); pub static SMTP_SERVER: OnceLock = OnceLock::new(); +pub static ERROR_EMAIL_ADDRESSES: OnceLock> = OnceLock::new(); lazy_static! { pub static ref DB: Arc = { @@ -147,6 +148,9 @@ pub async fn run(config: RelayerConfig) -> Result<()> { .unwrap(); SAFE_API_ENDPOINT.set(config.safe_api_endpoint).unwrap(); SMTP_SERVER.set(config.smtp_server).unwrap(); + ERROR_EMAIL_ADDRESSES + .set(config.error_email_addresses) + .unwrap(); let relayer_rand = derive_relayer_rand(PRIVATE_KEY.get().unwrap())?; RELAYER_RAND.set(field2hex(&relayer_rand.0)).unwrap(); @@ -324,6 +328,7 @@ async fn catch_claims_in_db_fn() -> Result<()> { error!(LOG, "Error voider task: {}", err; "func" => function_name!()); EmailWalletEvent::Error { email_addr, + error_subject: "Voiding claim".to_string(), error: err.to_string(), } } diff --git a/packages/relayer/src/modules/claimer.rs b/packages/relayer/src/modules/claimer.rs index db5e3333..3f3901fe 100644 --- a/packages/relayer/src/modules/claimer.rs +++ b/packages/relayer/src/modules/claimer.rs @@ -24,7 +24,7 @@ pub struct Claim { #[named] pub async fn claim_unclaims(mut claim: Claim) -> Result { - let mut need_creation = true; + let need_creation = true; let is_seen = claim.is_seen; if DB .get_claims_by_id(&claim.id) diff --git a/packages/relayer/src/modules/mail.rs b/packages/relayer/src/modules/mail.rs index ca12f3cb..469eeb86 100644 --- a/packages/relayer/src/modules/mail.rs +++ b/packages/relayer/src/modules/mail.rs @@ -46,6 +46,7 @@ pub enum EmailWalletEvent { }, Error { email_addr: String, + error_subject: String, error: String, }, Ack { @@ -278,15 +279,19 @@ pub async fn handle_email_event(event: EmailWalletEvent) -> Result<()> { }; send_email(email).await?; } - EmailWalletEvent::Error { email_addr, error } => { + EmailWalletEvent::Error { + email_addr, + error_subject, + error, + } => { let error = parse_error(error)?; if let Some(error) = error { let subject = format!("Email Wallet Notification. Error occurred."); let body_plain = format!("Hi {}!\nError occurred: {}", email_addr, error); - let render_data = serde_json::json!({"userEmailAddr": email_addr, "errorMsg": error, "chainRPCExplorer": CHAIN_RPC_EXPLORER.get().unwrap()}); + let render_data = serde_json::json!({"userEmailAddr": email_addr, "chainRPCExplorer": CHAIN_RPC_EXPLORER.get().unwrap()}); let body_html = render_html("error.html", render_data).await?; let email = EmailMessage { - to: email_addr, + to: email_addr.clone(), subject, body_plain, body_html, @@ -295,6 +300,25 @@ pub async fn handle_email_event(event: EmailWalletEvent) -> Result<()> { body_attachments: None, }; send_email(email).await?; + + // Send error email to team email addresses + let error_email_addresses = ERROR_EMAIL_ADDRESSES.get().unwrap(); + for error_email_addr in error_email_addresses { + let subject = format!("Email Wallet Notification. Error occurred."); + let body_plain = format!("Error occurred"); + let render_data = serde_json::json!({"userEmailAddr": error_email_addr, "error": error, "subject": error_subject, "emailAddr": email_addr}); + let body_html = render_html("error_alert.html", render_data).await?; + let email = EmailMessage { + to: error_email_addr.clone(), + subject: subject.clone(), + body_plain: body_plain.clone(), + body_html: body_html.clone(), + reference: None, + reply_to: None, + body_attachments: None, + }; + send_email(email).await?; + } } } EmailWalletEvent::Ack { diff --git a/packages/relayer/src/modules/web_server/rest_api.rs b/packages/relayer/src/modules/web_server/rest_api.rs index 531b5c68..5999f989 100644 --- a/packages/relayer/src/modules/web_server/rest_api.rs +++ b/packages/relayer/src/modules/web_server/rest_api.rs @@ -349,6 +349,7 @@ pub async fn receive_email_api_fn(email: String) -> Result<()> { error!(LOG, "Error handling email: {:?}", e); match handle_email_event(EmailWalletEvent::Error { email_addr: from_addr, + error_subject: parsed_email.get_subject_all().unwrap_or_default(), error: e.to_string(), }) .await diff --git a/packages/relayer/src/utils/strings.rs b/packages/relayer/src/utils/strings.rs index 31b141bf..c8e6bf09 100644 --- a/packages/relayer/src/utils/strings.rs +++ b/packages/relayer/src/utils/strings.rs @@ -1,5 +1,6 @@ // Config strings pub const SMTP_SERVER_KEY: &str = "SMTP_SERVER"; +pub const ERROR_EMAIL_ADDRESSES_KEY: &str = "ERROR_EMAIL_ADDRESSES"; pub const DATABASE_PATH_KEY: &str = "DATABASE_URL"; pub const WEB_SERVER_ADDRESS_KEY: &str = "WEB_SERVER_ADDRESS"; pub const CIRCUITS_DIR_PATH_KEY: &str = "CIRCUITS_DIR_PATH"; diff --git a/packages/relayer/src/utils/utils.rs b/packages/relayer/src/utils/utils.rs index 99575e7c..bf59ff9d 100644 --- a/packages/relayer/src/utils/utils.rs +++ b/packages/relayer/src/utils/utils.rs @@ -93,7 +93,7 @@ pub async fn compute_psi_point( info!(LOG, "command_str: {}", command_str); - let mut proc = tokio::process::Command::new("yarn") + let proc = tokio::process::Command::new("yarn") .args(command_str.split_whitespace()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) From d965247709cddc1a5e7ef8e69c804c011df40672 Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Sun, 4 Aug 2024 17:07:25 -0700 Subject: [PATCH 2/4] chore: update .env.example --- packages/relayer/.env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/relayer/.env.example b/packages/relayer/.env.example index 5e43cf08..c403ac9b 100644 --- a/packages/relayer/.env.example +++ b/packages/relayer/.env.example @@ -5,6 +5,7 @@ CHAIN_RPC_EXPLORER= CHAIN_ID=11155111 # Chain ID of the testnet. SMTP_SERVER= +ERROR_EMAIL_ADDRESSES= PROVER_ADDRESS="https://zkemail--email-wallet-relayer-v1-1-flask-app-dev.modal.run" From db8d10fca24126e49893ca7006b944fb31741aae Mon Sep 17 00:00:00 2001 From: Aditya Bisht Date: Sun, 4 Aug 2024 18:01:33 -0700 Subject: [PATCH 3/4] chore: update k8s manifest --- kubernetes/cronjob.yml | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/kubernetes/cronjob.yml b/kubernetes/cronjob.yml index e1362bbe..c66f0398 100644 --- a/kubernetes/cronjob.yml +++ b/kubernetes/cronjob.yml @@ -1,14 +1,45 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cronjob-service-account + namespace: email-wallet +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: email-wallet + name: deployment-restart-role +rules: + - apiGroups: ["apps", "extensions"] + resources: ["deployments"] + verbs: ["get", "list", "watch", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: deployment-restart-rolebinding + namespace: email-wallet +subjects: + - kind: ServiceAccount + name: cronjob-service-account + namespace: email-wallet +roleRef: + kind: Role + name: deployment-restart-role + apiGroup: rbac.authorization.k8s.io +--- apiVersion: batch/v1 kind: CronJob metadata: name: restart-deployment namespace: email-wallet spec: - schedule: "0 0 * * 0" # Runs every Sunday at midnight + schedule: "0 * * * *" jobTemplate: spec: template: spec: + serviceAccountName: cronjob-service-account containers: - name: kubectl image: bitnami/kubectl:latest From d687582b059aebd063d111143411cc15e45221c6 Mon Sep 17 00:00:00 2001 From: Dimitri Date: Mon, 26 Aug 2024 12:56:54 +0100 Subject: [PATCH 4/4] Check for failed delivery error in email headers --- packages/relayer/src/lib.rs | 1 + packages/relayer/src/modules/mail.rs | 20 +++++++++++++++---- .../src/modules/web_server/rest_api.rs | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/relayer/src/lib.rs b/packages/relayer/src/lib.rs index d0f32c95..dd3dd237 100644 --- a/packages/relayer/src/lib.rs +++ b/packages/relayer/src/lib.rs @@ -330,6 +330,7 @@ async fn catch_claims_in_db_fn() -> Result<()> { email_addr, error_subject: "Voiding claim".to_string(), error: err.to_string(), + email_headers: None, } } }; diff --git a/packages/relayer/src/modules/mail.rs b/packages/relayer/src/modules/mail.rs index 469eeb86..d68c3bec 100644 --- a/packages/relayer/src/modules/mail.rs +++ b/packages/relayer/src/modules/mail.rs @@ -1,6 +1,6 @@ use crate::*; use handlebars::Handlebars; -use relayer_utils::AccountCode; +use relayer_utils::{AccountCode, EmailHeaders}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::sync::atomic::Ordering; @@ -48,6 +48,7 @@ pub enum EmailWalletEvent { email_addr: String, error_subject: String, error: String, + email_headers: Option, }, Ack { email_addr: String, @@ -190,7 +191,6 @@ pub async fn handle_email_event(event: EmailWalletEvent) -> Result<()> { email_addr, CHAIN_RPC_EXPLORER.get().unwrap(), wallet_addr, ); let render_data = serde_json::json!({"userEmailAddr": email_addr, "walletAddr": wallet_addr, "assetsList": assets_list_html, "chainRPCExplorer": CHAIN_RPC_EXPLORER.get().unwrap()}); - println!("render_data: {:?}", render_data); let body_html = render_html("invitation.html", render_data).await?; let email = EmailMessage { to: email_addr.to_string(), @@ -283,8 +283,9 @@ pub async fn handle_email_event(event: EmailWalletEvent) -> Result<()> { email_addr, error_subject, error, + email_headers, } => { - let error = parse_error(error)?; + let error = parse_error(error, email_headers)?; if let Some(error) = error { let subject = format!("Email Wallet Notification. Error occurred."); let body_plain = format!("Hi {}!\nError occurred: {}", email_addr, error); @@ -361,7 +362,7 @@ pub async fn render_html(template_name: &str, render_data: Value) -> Result Result> { +pub fn parse_error(error: String, email_headers: Option) -> Result> { let mut error = error; if error.contains("Contract call reverted with data: ") { let revert_data = error @@ -380,6 +381,17 @@ pub fn parse_error(error: String) -> Result> { match error.as_str() { "Account is already created" => Ok(Some(error)), "insufficient balance" => Ok(Some("You don't have sufficient balance".to_string())), + "The user of email address mailer-daemon@googlemail.com is not registered." => { + let failed_recipients = email_headers + .and_then(|headers| headers.get_header("X-Failed-Recipients")) + .and_then(|v| v.first().cloned()) + .unwrap_or_else(|| "unknown recipient".to_string()); + + Ok(Some(format!( + "Failed to deliver email to {}. The address does not exist.", + failed_recipients + ))) + } _ => Ok(Some(error)), } } diff --git a/packages/relayer/src/modules/web_server/rest_api.rs b/packages/relayer/src/modules/web_server/rest_api.rs index 5999f989..96668f59 100644 --- a/packages/relayer/src/modules/web_server/rest_api.rs +++ b/packages/relayer/src/modules/web_server/rest_api.rs @@ -351,6 +351,7 @@ pub async fn receive_email_api_fn(email: String) -> Result<()> { email_addr: from_addr, error_subject: parsed_email.get_subject_all().unwrap_or_default(), error: e.to_string(), + email_headers: Some(parsed_email.headers.clone()), }) .await {