Skip to content

Commit

Permalink
Merge pull request #1 from amritghimire/mailersend
Browse files Browse the repository at this point in the history
  • Loading branch information
amritghimire authored Apr 28, 2024
2 parents dcc5998 + 5ed4cae commit c62500a
Show file tree
Hide file tree
Showing 19 changed files with 1,214 additions and 65 deletions.
617 changes: 615 additions & 2 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lettre = { version = "0.11.6", features = ["tracing", "tokio1-native-tls", "toki
thiserror = "1.0.58"
log = "0.4.21"
document-features = { version = "0.2", optional = true }
reqwest = { version = "0.12.4", optional = true, features = ["json"] }



Expand All @@ -39,19 +40,20 @@ memory = []
### Enable smtp client based on lettre.
smtp = ["dep:secrecy", "dep:lettre"]

### Upcoming feature for mailsend.
mailsend = []
### Send email using mailersend
mailersend = ["dep:secrecy", "dep:reqwest"]

[dev-dependencies]
tokio-test = "0.4.4"
wiremock = "0.6.0"

[package.metadata.cargo-udeps.ignore]
normal = ["log"]
development = ["tokio-test"]
development = ["tokio-test", "wiremock"]

# docs.rs-specific configuration
[package.metadata.docs.rs]
# document all features
all-features = true
# defines the configuration attribute `docsrs`
rustdoc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Based on the email client you want to support, you need to initialize email conf
```rust
async fn send_email() {
let email = EmailObject {
sender: "[email protected]".to_string(),
sender: "[email protected]",
to: vec![EmailAddress { name: "Mail".to_string(), email: "[email protected]".to_string() }],
subject: "subject".to_string(),
plain: "plain body".to_string(),
Expand Down
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ allow = [
"MIT",
"Apache-2.0",
"Unicode-DFS-2016",
"BSD-3-Clause",
"0BSD"
#"Apache-2.0 WITH LLVM-exception",
]
Expand Down
257 changes: 257 additions & 0 deletions src/clients/mailersend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
use crate::configuration::EmailConfiguration;
use crate::email::{EmailAddress, EmailObject};
use crate::traits::EmailTrait;
use crate::Result;
use async_trait::async_trait;
use reqwest::header::HeaderMap;
use reqwest::{header, Client, Method};
use secrecy::{ExposeSecret, Secret};

static BASE_URL: &str = "https://api.mailersend.com/v1";

fn default_base_url() -> String {
BASE_URL.to_string()
}

/// `MailerSendConfig` structure that includes sender, base_url, and api_token.
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
///
/// let mut mailer_send_config = MailerSendConfig::default()
/// .sender("[email protected]")
/// .base_url("https://api.mailersend.com/v1")
/// .api_token("test_api_token");
/// assert_eq!(mailer_send_config.get_sender().to_string(), "[email protected]");
/// assert_eq!(mailer_send_config.get_base_url(), "https://api.mailersend.com/v1");
/// ```
#[derive(Debug, Clone, serde::Deserialize)]
pub struct MailerSendConfig {
sender: EmailAddress,
#[serde(default = "default_base_url")]
base_url: String,
api_token: Secret<String>,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
struct EmailPayload {
from: EmailAddress,
to: Vec<EmailAddress>,
subject: String,
text: String,
html: String,
}

impl From<EmailObject> for EmailPayload {
fn from(value: EmailObject) -> Self {
Self {
from: value.sender,
to: value.to,
subject: value.subject,
text: value.plain,
html: value.html,
}
}
}

impl Default for MailerSendConfig {
/// Constructs a `MailerSendConfig` with default values:
/// - sender: An empty string `""`
/// - base_url: `https://api.mailersend.com/v1`
/// - api_token: An empty string `""`
///
/// # Examples
///
/// Basic usage:
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
///
/// let config = MailerSendConfig::default();
///
/// assert_eq!(config.get_sender().to_string(), "");
/// assert_eq!(config.get_base_url(), "https://api.mailersend.com/v1");
/// ```
///
fn default() -> Self {
Self {
sender: "".into(),
base_url: BASE_URL.to_string(),
api_token: Secret::from("".to_string()),
}
}
}

impl MailerSendConfig {
/// Sets the sender of the Mailersend config.
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
///
/// let mut smtp_config = MailerSendConfig::default().sender("Test Sender");
/// assert_eq!(smtp_config.get_sender().to_string(), "Test Sender");
/// ```
pub fn sender(mut self, value: impl Into<EmailAddress>) -> Self {
self.sender = value.into();
self
}

/// Sets the base_url of the Mailersend config.
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
///
/// let mut smtp_config = MailerSendConfig::default().base_url("Test URL");
/// assert_eq!(smtp_config.get_base_url(), "Test URL");
/// ```
pub fn base_url(mut self, value: impl AsRef<str>) -> Self {
self.base_url = value.as_ref().trim_end_matches('/').to_string();
self
}

/// Sets the api_token of the Mailersend config.
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
///
/// let mut smtp_config = MailerSendConfig::default().api_token("Test Token");
/// ```
pub fn api_token(mut self, value: impl AsRef<str>) -> Self {
self.api_token = Secret::new(value.as_ref().to_string());
self
}

/// Returns the base url of the Mailersend config.
///
/// # Example
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
///
/// let smtp_config = MailerSendConfig::default().base_url("https://api.mailersend.com/v1");
/// assert_eq!(smtp_config.get_base_url(), "https://api.mailersend.com/v1");
/// ```
///
pub fn get_base_url(&self) -> String {
self.base_url.to_string()
}

/// Returns the sender of the Mailersend config.
///
/// # Example
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
///
/// let mailer_send_config = MailerSendConfig::default().sender("[email protected]");
/// assert_eq!(mailer_send_config.get_sender().to_string(), "[email protected]");
/// ```
///
pub fn get_sender(&self) -> EmailAddress {
self.sender.clone()
}
}

impl From<MailerSendConfig> for EmailConfiguration {
/// Converts a `MailerSendConfig` into an `EmailConfiguration`
///
/// This conversion is mainly used when we are setting the configuration for our email client.
///
/// # Example
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
/// use email_clients::configuration::EmailConfiguration;
///
/// let mailer_config = MailerSendConfig::default()
/// .sender("[email protected]")
/// .base_url("https://api.mailersend.com/v1")
/// .api_token("test_api_token");
///
/// let email_config: EmailConfiguration = mailer_config.into();
/// ```
fn from(value: MailerSendConfig) -> Self {
EmailConfiguration::Mailersend(value)
}
}

/// `MailerSendClient` structure that includes 'config' and 'reqwest_client'.
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
/// use email_clients::clients::mailersend::MailerSendClient;
///
/// let mailer_send_config = MailerSendConfig::default()
/// .sender("[email protected]")
/// .base_url("https://api.mailersend.com/v1")
/// .api_token("test_api_token");
/// let mailer_send_client = MailerSendClient::new(mailer_send_config);
/// ```
#[derive(Clone, Debug, Default)]
pub struct MailerSendClient {
config: MailerSendConfig,
reqwest_client: Client,
}

impl MailerSendClient {
pub fn new(config: MailerSendConfig) -> Self {
let reqwest_client = Client::new();

MailerSendClient {
config,
reqwest_client,
}
}

fn url(&self) -> String {
format!("{}/email", self.config.base_url.trim_end_matches('/'))
}

fn headers(&self) -> Result<HeaderMap> {
let mut headers = HeaderMap::new();
headers.insert(
header::AUTHORIZATION,
format!("Bearer {}", self.config.api_token.expose_secret()).parse()?,
);
Ok(headers)
}
}

#[async_trait]
impl EmailTrait for MailerSendClient {
/// Returns the sender included in the `MailerSendClient`'s configuration.
///
/// # Examples
///
/// Basic usage:
///
/// ```rust
/// use email_clients::clients::mailersend::MailerSendConfig;
/// use email_clients::clients::mailersend::MailerSendClient;
/// use email_clients::traits::EmailTrait;
///
/// let mailer_send_config = MailerSendConfig::default()
/// .sender("[email protected]")
/// .base_url("https://api.mailersend.com/v1")
/// .api_token("test_api_token");
///
/// let mailer_send_client = MailerSendClient::new(mailer_send_config);
///
/// assert_eq!(mailer_send_client.get_sender().to_string(), "[email protected]");
/// ```
fn get_sender(&self) -> EmailAddress {
self.config.get_sender().clone()
}

async fn send_emails(&self, email: EmailObject) -> Result<()> {
let payload: EmailPayload = email.into();
self.reqwest_client
.request(Method::POST, self.url())
.headers(self.headers()?)
.json(&payload)
.send()
.await?
.error_for_status()?;
Ok(())
}
}
Loading

0 comments on commit c62500a

Please sign in to comment.