stormchaser-engine 1.4.2

A robust, distributed workflow engine for event-driven and human-triggered workflows.
Documentation
#[cfg(feature = "aws-ses")]
use anyhow::{Context, Result};

#[cfg(feature = "aws-ses")]
pub async fn build_ses_client(
    region: Option<String>,
    role_arn: Option<String>,
    run_id: stormchaser_model::RunId,
) -> Result<aws_sdk_ses::Client> {
    let mut config_loader = aws_config::defaults(aws_config::BehaviorVersion::v2026_01_12());
    if let Some(r) = region {
        config_loader = config_loader.region(aws_config::Region::new(r));
    }
    let config = config_loader.load().await;

    if let Some(role) = role_arn {
        let sts_client = aws_sdk_sts::Client::new(&config);
        let session_name = format!("stormchaser-ses-{}", run_id);

        let assume_role_res = sts_client
            .assume_role()
            .role_arn(role)
            .role_session_name(session_name)
            .send()
            .await?;

        let credentials = assume_role_res
            .credentials()
            .context("Missing credentials from assume_role")?;

        let assumed_credentials = aws_sdk_ses::config::Credentials::new(
            credentials.access_key_id(),
            credentials.secret_access_key(),
            Some(credentials.session_token().to_string()),
            None,
            "StsAssumedRole",
        );

        let provider = aws_sdk_ses::config::SharedCredentialsProvider::new(assumed_credentials);
        let assumed_config = aws_sdk_ses::config::Builder::from(&config)
            .credentials_provider(provider)
            .build();

        Ok(aws_sdk_ses::Client::from_conf(assumed_config))
    } else {
        Ok(aws_sdk_ses::Client::new(&config))
    }
}

#[cfg(feature = "aws-ses")]
#[allow(clippy::too_many_arguments)]
pub async fn send_email_ses(
    from: String,
    to: Vec<String>,
    cc: Option<Vec<String>>,
    bcc: Option<Vec<String>>,
    subject: String,
    body: String,
    html: bool,
    region: Option<String>,
    role_arn: Option<String>,
    configuration_set_name: Option<String>,
    run_id: stormchaser_model::RunId,
) -> Result<()> {
    use aws_sdk_ses::types::{Body, Content, Destination, Message};

    let client = build_ses_client(region, role_arn, run_id).await?;

    let mut dest_builder = Destination::builder();
    for addr in to {
        dest_builder = dest_builder.to_addresses(addr);
    }
    if let Some(ccs) = cc {
        for addr in ccs {
            dest_builder = dest_builder.cc_addresses(addr);
        }
    }
    if let Some(bccs) = bcc {
        for addr in bccs {
            dest_builder = dest_builder.bcc_addresses(addr);
        }
    }
    let destination = dest_builder.build();

    let content = Content::builder().data(body).charset("UTF-8").build()?;
    let mut body_builder = Body::builder();
    if html {
        body_builder = body_builder.html(content);
    } else {
        body_builder = body_builder.text(content);
    }

    let message = Message::builder()
        .subject(Content::builder().data(subject).charset("UTF-8").build()?)
        .body(body_builder.build())
        .build();

    let mut request = client
        .send_email()
        .source(from)
        .destination(destination)
        .message(message);

    if let Some(cs) = configuration_set_name {
        request = request.configuration_set_name(cs);
    }

    request.send().await?;

    Ok(())
}