Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions rook/Cargo.lock

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

1 change: 1 addition & 0 deletions rook/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
shuttle-axum = "0.55.0"
shuttle-runtime = { version = "0.55.0", default-features = false } # see https://docs.shuttle.dev/docs/logs#default-tracing-subscriber
validator = "0.20"
secrecy = { version = "0.8", features = ["serde"] }

[dev-dependencies]
serial_test = "3.0.0"
Expand Down
2 changes: 1 addition & 1 deletion rook/src/domain/subscriber_email.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde::Deserialize;
use validator::ValidateEmail;

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
pub struct SubscriberEmail(String);

impl SubscriberEmail {
Expand Down
71 changes: 71 additions & 0 deletions rook/src/email_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use crate::domain::SubscriberEmail;
use reqwest::Client;
use secrecy::{ExposeSecret, Secret};
use std::time::Duration;

#[derive(Clone)]
pub struct EmailClient {
http_client: Client,
base_url: String,
sender: SubscriberEmail,
authorization_token: Secret<String>,
}

impl EmailClient {
pub fn new(
base_url: String,
sender: SubscriberEmail,
authorization_token: Secret<String>,
timeout: Duration,
) -> Self {
let http_client = Client::builder().timeout(timeout).build().unwrap();
Self {
http_client,
base_url,
sender,
authorization_token,
}
Comment on lines +20 to +27
Copy link

Copilot AI May 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using unwrap() here can cause a panic if the HTTP client cannot be built. It is recommended to handle this error gracefully, such as by propagating the error using Result.

Suggested change
) -> Self {
let http_client = Client::builder().timeout(timeout).build().unwrap();
Self {
http_client,
base_url,
sender,
authorization_token,
}
) -> Result<Self, reqwest::Error> {
let http_client = Client::builder().timeout(timeout).build()?;
Ok(Self {
http_client,
base_url,
sender,
authorization_token,
})

Copilot uses AI. Check for mistakes.
}

pub async fn send_email(
&self,
recipient: SubscriberEmail,
subject: String,
html_content: String,
text_content: String,
) -> Result<(), String> {
let url = format!("{}/email", self.base_url);
let request_body = SendEmailRequest {
from: self.sender.as_ref().to_owned(),
to: recipient.as_ref().to_owned(),
subject: subject.to_owned(),
html_body: html_content.to_owned(),
text_body: text_content.to_owned(),
};

let _ = self
.http_client
.post(&url)
.header(
"X-Postmark-Server-Token",
self.authorization_token.expose_secret(),
)
.json(&request_body)
.send()
.await
.map_err(|e| format!("Failed to send email: {}", e))?
.error_for_status();
Copy link

Copilot AI May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Result from .error_for_status() is ignored, so non-success HTTP status codes will not be propagated. Append "?" or handle the error to ensure failures are surfaced.

Suggested change
.error_for_status();
.error_for_status()
.map_err(|e| format!("Email request failed with HTTP error: {}", e))?;

Copilot uses AI. Check for mistakes.

Ok(())
}
}

#[derive(serde::Serialize)]
#[serde(rename_all = "PascalCase")]
struct SendEmailRequest {
from: String,
to: String,
subject: String,
html_body: String,
text_body: String,
}
2 changes: 2 additions & 0 deletions rook/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub mod domain;
pub mod email_client;
pub mod git;
pub mod link;
pub mod schedule;

pub use domain::*;
pub use email_client::*;
pub use git::*;
pub use link::*;
pub use schedule::*;