amail-lambda-smtp-relay 0.1.0

Amail incoming SMTP relay
#[macro_use]
extern crate log;

use std::boxed::Box;
use std::env;
use std::error::Error;
use std::str::FromStr;

use aws_lambda_events::event::ses::SimpleEmailEvent;
use lambda_runtime::error::HandlerError;
use lambda_runtime::lambda;
use lambda_runtime::Context;
use lettre::transport::smtp;
use lettre::Transport;
use rusoto_core::Region;
use rusoto_s3::GetObjectRequest;
use rusoto_s3::S3Client;
use rusoto_s3::S3;
use serde_derive::Serialize;
use tokio::io::AsyncReadExt;

#[derive(Serialize)]
struct Output {}

fn main() -> Result<(), Box<dyn Error>> {
    stderrlog::new()
        .modules(vec![module_path!(), "lettre"])
        .verbosity(2)
        .timestamp(stderrlog::Timestamp::Off)
        .init()
        .unwrap();

    lambda!(handler);
    Ok(())
}

fn handler(e: SimpleEmailEvent, _: Context) -> Result<Output, HandlerError> {
    let region = env::var("S3_REGION")
        .expect("Missing $S3_REGION")
        .parse::<Region>()
        .expect("Invalid S3 region");
    let s3 = S3Client::new(region);

    let smtp_host = env::var("SMTP_HOST").expect("Missing $SMTP_HOST");
    let mut smtp = smtp::SmtpTransport::relay(&smtp_host)
        .expect("Failed to create SMTP client")
        .credentials(smtp::authentication::Credentials::new(
            env::var("SMTP_USER").expect("Missing $SMTP_USER"),
            env::var("SMTP_PASS").expect("Missing $SMTP_PASS"),
        ))
        .build();

    let mail_event = &e.records[0].ses.mail;
    let message_id = mail_event
        .message_id
        .as_ref()
        .expect("Unknown SES messageId");

    info!("Relaying {}", message_id);

    relay_eml(&mut smtp, &s3, message_id);
    Ok(Output {})
}

#[tokio::main]
async fn relay_eml(smtp: &mut smtp::SmtpTransport, s3: &S3Client, message_id: &str) {
    let mut content = Vec::new();
    s3.get_object(GetObjectRequest {
        bucket: env::var("S3_BUCKET").expect("Missing $S3_BUCKET"),
        key: message_id.into(),
        ..Default::default()
    })
    .await
    .unwrap_or_else(|_| panic!("Failed to retrieve {}", message_id))
    .body
    .unwrap()
    .into_async_read()
    .read_to_end(&mut content)
    .await
    .unwrap_or_else(|_| panic!("Failed to read {}", message_id));

    let relay_from = lettre::Address::from_str(
        &env::var("RELAY_ENVELOPE_FROM").expect("Missing $RELAY_ENVELOPE_FROM"),
    )
    .expect("Malformed $RELAY_ENVELOPE_FROM");

    let relay_to = lettre::Address::from_str(
        &env::var("RELAY_ENVELOPE_TO").expect("Missing $RELAY_ENVELOPE_TO"),
    )
    .expect("Malformed $RELAY_ENVELOPE_FROM");

    let envelope =
        lettre::Envelope::new(Some(relay_from), vec![relay_to]).expect("Failed to build envelope");

    smtp.send_raw(&envelope, &content)
        .unwrap_or_else(|_| panic!("Failed to send {}", message_id));
}