#[cfg(feature = "aws-sdk-sesv2")]
use async_trait::async_trait;
#[cfg(feature = "aws-sdk-sesv2")]
use aws_sdk_sesv2::{
types::{Body, Content, Destination, EmailContent, Message},
Client,
};
use crate::email::{Email, EmailError, EmailSender};
#[cfg(feature = "aws-sdk-sesv2")]
pub struct AwsSesBackend {
client: Client,
}
#[cfg(feature = "aws-sdk-sesv2")]
impl AwsSesBackend {
#[must_use]
pub fn new(client: Client) -> Self {
Self { client }
}
pub async fn from_env() -> Result<Self, EmailError> {
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
.load()
.await;
let client = Client::new(&config);
Ok(Self::new(client))
}
fn build_message(&self, email: &Email) -> Result<EmailContent, EmailError> {
email.validate()?;
let mut destination = Destination::builder();
for to_addr in &email.to {
destination = destination.to_addresses(to_addr);
}
for cc_addr in &email.cc {
destination = destination.cc_addresses(cc_addr);
}
for bcc_addr in &email.bcc {
destination = destination.bcc_addresses(bcc_addr);
}
let subject = email.subject.as_ref().ok_or(EmailError::NoSubject)?;
let subject_content = Content::builder().data(subject).build().map_err(|e| {
EmailError::aws_ses(format!("Failed to build subject content: {e}"))
})?;
let body = if let (Some(html), Some(text)) = (&email.html, &email.text) {
let html_content = Content::builder().data(html).build().map_err(|e| {
EmailError::aws_ses(format!("Failed to build HTML content: {e}"))
})?;
let text_content = Content::builder().data(text).build().map_err(|e| {
EmailError::aws_ses(format!("Failed to build text content: {e}"))
})?;
Body::builder()
.html(html_content)
.text(text_content)
.build()
} else if let Some(html) = &email.html {
let html_content = Content::builder().data(html).build().map_err(|e| {
EmailError::aws_ses(format!("Failed to build HTML content: {e}"))
})?;
Body::builder().html(html_content).build()
} else if let Some(text) = &email.text {
let text_content = Content::builder().data(text).build().map_err(|e| {
EmailError::aws_ses(format!("Failed to build text content: {e}"))
})?;
Body::builder().text(text_content).build()
} else {
return Err(EmailError::NoContent);
};
let message = Message::builder()
.subject(subject_content)
.body(body)
.build()
.map_err(|e| EmailError::aws_ses(format!("Failed to build message: {e}")))?;
Ok(EmailContent::builder().simple(message).build())
}
}
#[cfg(feature = "aws-sdk-sesv2")]
#[async_trait]
impl EmailSender for AwsSesBackend {
async fn send(&self, email: Email) -> Result<(), EmailError> {
let from_addr = email.from.as_ref().ok_or(EmailError::NoSender)?;
let content = self.build_message(&email)?;
let mut request = self
.client
.send_email()
.from_email_address(from_addr)
.content(content);
if let Some(reply_to) = &email.reply_to {
request = request.reply_to_addresses(reply_to);
}
request
.send()
.await
.map_err(|e| EmailError::aws_ses(format!("Failed to send email: {e}")))?;
Ok(())
}
}
#[cfg(not(feature = "aws-sdk-sesv2"))]
pub struct AwsSesBackend;
#[cfg(not(feature = "aws-sdk-sesv2"))]
impl AwsSesBackend {
#[allow(clippy::unused_async)]
pub async fn from_env() -> Result<Self, EmailError> {
Err(EmailError::config(
"AWS SES backend requires the 'aws-sdk-sesv2' feature to be enabled",
))
}
}
#[cfg(not(feature = "aws-sdk-sesv2"))]
#[async_trait::async_trait]
impl EmailSender for AwsSesBackend {
async fn send(&self, _email: Email) -> Result<(), EmailError> {
Err(EmailError::config(
"AWS SES backend requires the 'aws-sdk-sesv2' feature to be enabled",
))
}
}
#[cfg(all(test, feature = "aws-sdk-sesv2"))]
mod tests {
use super::*;
#[test]
fn test_build_message_simple() {
let email = Email::new()
.to("recipient@example.com")
.from("sender@example.com")
.subject("Test Email")
.text("This is a test email");
let config = aws_sdk_sesv2::Config::builder().build();
let client = Client::from_conf(config);
let backend = AwsSesBackend::new(client);
let content = backend.build_message(&email);
assert!(content.is_ok());
}
#[test]
fn test_build_message_with_html_and_text() {
let email = Email::new()
.to("recipient@example.com")
.from("sender@example.com")
.subject("Test Email")
.text("This is plain text")
.html("<h1>This is HTML</h1>");
let config = aws_sdk_sesv2::Config::builder().build();
let client = Client::from_conf(config);
let backend = AwsSesBackend::new(client);
let content = backend.build_message(&email);
assert!(content.is_ok());
}
}