scufflecloud_email/
aws_ses.rs1use std::sync::Arc;
2
3use base64::Engine;
4use email_traits::AwsInterface;
5use reqwest::Method;
6
7#[derive(Debug, thiserror::Error)]
8pub(crate) enum SendEmailError {
9 #[error("serialization error: {0}")]
10 Serialization(#[from] serde_json::Error),
11 #[error("request builder error: {0}")]
12 Request(#[from] http::Error),
13 #[error("HTTP error: {0}")]
14 Http(#[from] reqwest::Error),
15 #[error("failed to sign request: {0}")]
16 Signer(#[from] reqsign::Error),
17}
18
19pub(crate) async fn send_email<G: email_traits::HttpClientInterface + AwsInterface>(
20 global: &Arc<G>,
21 raw_email: Vec<u8>,
22) -> Result<(), SendEmailError> {
23 let data = base64::prelude::BASE64_STANDARD.encode(raw_email);
26 let payload = SendEmailPayload {
27 content: EmailContent::Raw(RawMessage { data }),
28 };
29
30 let req_body = serde_json::to_vec(&payload)?;
31 let mut req_parts = http::Request::builder()
32 .method(Method::POST)
33 .uri(format!(
34 "https://email.{}.amazonaws.com/v2/email/outbound-emails",
35 global.aws_region()
36 ))
37 .header("content-type", "application/x-amz-json-1.1")
38 .header("x-amz-content-sha256", &reqsign::hash::hex_sha256(&req_body))
39 .body(())? .into_parts()
41 .0;
42
43 global.aws_ses_req_signer().sign(&mut req_parts, None).await?;
44
45 let req = http::Request::from_parts(req_parts, req_body).try_into()?;
46 global.external_http_client().execute(req).await?.error_for_status()?;
47
48 Ok(())
49}
50
51#[derive(serde_derive::Serialize)]
53#[serde(rename_all = "PascalCase")]
54struct SendEmailPayload {
55 content: EmailContent,
56}
57
58#[derive(serde_derive::Serialize)]
60#[serde(rename_all = "PascalCase")]
61enum EmailContent {
62 Raw(RawMessage),
63}
64
65#[derive(serde_derive::Serialize)]
67#[serde(rename_all = "PascalCase")]
68struct RawMessage {
69 data: String,
71}