m2 0.0.0

Set of Unix tools to work with m2dirs
Documentation
use std::{
    error::Error,
    io::{Read, stdin},
};

use clap::Parser;
use mail_parser::{Addr, Address, Message, MessageParser};
use mail_send::{SmtpClientBuilder, smtp::message::Message as SmtpMessage};

#[derive(Parser)]
struct Args {
    #[arg(short, long)]
    username: String,
    #[arg(short, long)]
    password: String,
    #[arg(short, long)]
    host: String,
    #[arg(short, long, default_value_t = 465)]
    port: u16,
}

async fn run() -> Result<(), Box<dyn Error>> {
    let args = Args::try_parse()?;

    let mut bytes = Vec::new();
    stdin().read_to_end(&mut bytes)?;

    let msg = MessageParser::new()
        .parse(&bytes)
        .ok_or("could not send email as it is invalid: no headers found")?;

    eprintln!("Sending over SMTP...");
    SmtpClientBuilder::new(args.host, args.port)
        .credentials((args.username, args.password))
        .connect()
        .await?
        .send(parsed_msg_to_smtp_msg(&msg)?)
        .await?;

    Ok(())
}

#[tokio::main]
async fn main() {
    if let Err(e) = run().await {
        eprintln!("m2send: {e}");
    }
}

fn parsed_msg_to_smtp_msg<'a>(msg: &'a Message<'a>) -> Result<SmtpMessage<'a>, &'static str> {
    let from = msg
        .from()
        .and_then(Address::first)
        .and_then(Addr::address)
        .ok_or("missing sender")?;

    let to = msg
        .to()
        .and_then(Address::as_list)
        .unwrap_or_default()
        .iter()
        .flat_map(Addr::address);

    let cc = msg
        .cc()
        .and_then(Address::as_list)
        .unwrap_or_default()
        .iter()
        .flat_map(Addr::address);

    let bcc = msg
        .bcc()
        .and_then(Address::as_list)
        .unwrap_or_default()
        .iter()
        .flat_map(Addr::address);

    Ok(SmtpMessage::new(
        from,
        to.chain(cc).chain(bcc),
        msg.raw_message.as_ref(),
    ))
}