An ESMTP server library written in Rust.

Related tags

Email rs-smtp
Overview

rs-smtp

An ESMTP server library written in Rust.

Features

  • ESMTP client & server implementing RFC 5321
  • Support for SMTP AUTH and PIPELINING
  • UTF-8 support for subject and message body

Usage

Add this to your Cargo.toml:

[dependencies]
rs-smtp = "1"

anyhow = "1.0"
tokio = { version = "1.26.0", features = ["full"] }
async-trait = "0.1.67"

Example

Simple Example

use anyhow::Result;
use async_trait::async_trait;
use rs_smtp::sasl;
use tokio::io::{AsyncReadExt, AsyncRead};

use rs_smtp::backend::{Backend, Session, MailOptions};
use rs_smtp::conn::Conn;
use rs_smtp::server::Server;

struct MyBackend;

struct MySession;

impl Backend for MyBackend {
    type S = MySession;

    fn new_session(&self, _c: &mut Conn<Self>) -> Result<MySession> {
        Ok(MySession)
    }
}

#[async_trait]
impl Session for MySession {
    fn authenticators(&mut self) -> Vec<Box<dyn sasl::Server>> {
        vec!()
    }
    
    async fn mail(&mut self, from: &str, _: &MailOptions) -> Result<()> {
        println!("mail from: {}", from);
        Ok(())
    }

    async fn rcpt(&mut self, to: &str) -> Result<()> {
        println!("rcpt to: {}", to);
        Ok(())
    }
    
    async fn data<R: AsyncRead + Send + Unpin>(&mut self, mut r: R) -> Result<()> {
        // print whole message
        let mut mail = String::new();
        r.read_to_string(&mut mail).await?;
        println!("data: {}", mail);

        Ok(())
    }

    fn reset(&mut self) {}

    fn logout(&mut self) -> Result<()> {
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let be = MyBackend;

    let mut s = Server::new(be);

    s.addr = "127.0.0.1:2525".to_string();
    s.domain = "localhost".to_string();
    s.read_timeout = std::time::Duration::from_secs(10);
    s.write_timeout = std::time::Duration::from_secs(10);
    s.max_message_bytes = 10 * 1024 * 1024;
    s.max_recipients = 50;
    s.max_line_length = 1000;
    s.allow_insecure_auth = false;

    println!("Starting server on {}", s.addr);
    match s.listen_and_serve().await {
        Ok(_) => println!("Server stopped"),
        Err(e) => println!("Server error: {}", e),
    }

    Ok(())
}

You can use the server manually with telnet:

telnet localhost 2525
EHLO localhost
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
DATA
Hey <3
.

Outgoing Mail Server Example With Authentication and STARTTLS

use anyhow::Result;
use async_trait::async_trait;
use mail_send::SmtpClientBuilder;
use mail_send::mail_auth::common::crypto::{RsaKey, Sha256};
use mail_send::mail_auth::dkim::DkimSigner;
use rs_smtp::sasl;
use rustls_pemfile::{certs, pkcs8_private_keys};
use tokio::io::{self, AsyncReadExt, AsyncRead};
use tokio_rustls::rustls::{self, Certificate, PrivateKey};
use tokio_rustls::TlsAcceptor;

use std::borrow::Cow;
use trust_dns_resolver::AsyncResolver;
use trust_dns_resolver::config::*;

use mail_send::smtp::message::{
    Address,
    Message
};

use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;


use rs_smtp::backend::{Backend, Session, MailOptions};
use rs_smtp::conn::Conn;
use rs_smtp::sasl::plain::{PlainServer, PlainAuthenticator};
use rs_smtp::server::Server;

struct MyBackend;

struct MySession {
    to: Vec<String>,
}

impl Backend for MyBackend {
    type S = MySession;

    fn new_session(&self, _c: &mut Conn<Self>) -> Result<MySession> {
        Ok(MySession {
            to: Vec::new(),
        })
    }
}

struct MyPlainAuthenticator;

#[async_trait]
impl PlainAuthenticator for MyPlainAuthenticator {
    async fn authenticate(&mut self, _identity: &str, username: &str, password: &str) -> Result<()> {
        if username == "[email protected]" && password == "test" {
            Ok(())
        } else {
            Err(anyhow::anyhow!("invalid username or password"))
        }
    }
}

#[async_trait]
impl Session for MySession {
    fn authenticators(&mut self) -> Vec<Box<dyn sasl::Server>> {
        vec!(
            Box::new(PlainServer::new(MyPlainAuthenticator))
        )
    }

    async fn mail(&mut self, from: &str, _: &MailOptions) -> Result<()> {
        println!("mail from: {}", from);
        Ok(())
    }

    async fn rcpt(&mut self, to: &str) -> Result<()> {
        println!("rcpt to: {}", to);
        self.to.push(to.to_string());
        Ok(())
    }
    
    async fn data<R: AsyncRead + Send + Unpin>(&mut self, mut r: R) -> Result<()> {
        // print whole message
        let mut mail = vec![];
        r.read_to_end(&mut mail).await?;

        let resolver = AsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default())?;

        let pk_rsa = RsaKey::<Sha256>::from_rsa_pem(DKIM_KEY).unwrap();
        let signer = DkimSigner::from_key(pk_rsa)
            .domain("dunef.io")
            .selector("dkim_selector")
            .headers(["From", "To", "Subject"])
            .expiration(60 * 60 * 7);

        for to in self.to.iter() {

            println!("Sending email to {}", to);

            let mx_response = resolver.mx_lookup(to.split("@").collect::<Vec<&str>>()[1]).await?;

            for mx in mx_response.iter() {

                println!("MX: {}", mx.exchange());

                let builder = SmtpClientBuilder::new(mx.exchange().to_utf8(), 25)
                    .helo_host("dunef.io".to_string())
                    .implicit_tls(false)
                    .connect()
                    .await;

                if builder.is_err() {
                    println!("Failed to connect to {}", mx.exchange());
                    continue;
                }

                match builder.unwrap()
                    .send_signed(
                        Message::new(
                            Address::from("[email protected]"),
                            vec![Address::from(to.clone())],
                            Cow::from(&mail),
                        ),
                        &signer
                    )
                    .await {
                        Ok(_) => {
                            println!("Email sent successfully");
                            break;
                        },
                        Err(e) => {
                            println!("Failed to send email: {}", e);
                            continue;
                        }
                    }
            }
        }

        Ok(())
    }

    fn reset(&mut self) {}

    fn logout(&mut self) -> Result<()> {
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let be = MyBackend;

    let mut s = Server::new(be);

    s.addr = "127.0.0.1:587".to_string();
    s.domain = "dunef.io".to_string();
    s.read_timeout = std::time::Duration::from_secs(10);
    s.write_timeout = std::time::Duration::from_secs(10);
    s.max_message_bytes = 10 * 1024 * 1024;
    s.max_recipients = 50;
    s.max_line_length = 1000;
    s.allow_insecure_auth = false;

    let certs = load_certs("/etc/letsencrypt/live/dunef.io/fullchain.pem")?;
    let mut keys = load_keys("/etc/letsencrypt/live/dunef.io/privkey.pem")?;

    let config = rustls::ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_single_cert(certs, keys.remove(0))
        .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
    let acceptor = TlsAcceptor::from(Arc::new(config));

    s.tls_acceptor = Some(acceptor);

    println!("Starting server on {}", s.addr);
    match s.listen_and_serve().await {
        Ok(_) => println!("Server stopped"),
        Err(e) => println!("Server error: {}", e),
    }

    Ok(())
}

fn load_certs(path: &str) -> io::Result<Vec<Certificate>> {
    certs(&mut BufReader::new(File::open(path)?))
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
        .map(|mut certs| certs.drain(..).map(Certificate).collect())
}

fn load_keys(path: &str) -> io::Result<Vec<PrivateKey>> {
    pkcs8_private_keys(&mut BufReader::new(File::open(path)?))
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))
        .map(|mut keys| keys.drain(..).map(PrivateKey).collect())
}

const DKIM_KEY: &str = "-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAwbIlk19eVTa8RayErwcr0PWkH+IDZSddxVw2AKpujKJN642e
PiwMChgj5LRwFdWknT97E9YykVk46vNPdKTjBe1POuUrl2wcm46Hv0easuqiyjRC
WswNIN+avvG2krgMgf3/2T12lyJ2v4wcn34q9vqN+dkLfMz9WWbKEeKHHq80SFyL
PHA6ieTNToXgHgP+Qr5lbL6gh8fjTiUtpIVvHt8N29TIBTpVi3StE+2cu/RCSfcZ
6Wu6tJYzRxlTIKWFxU5Fh+5YL0gue3M7+1e5FyeQHChyRljpDWWywYPGrlN4Thon
KV2nwwV1v3B/1dBEKuXuo3lNPOrkwkc4rgsPvQIDAQABAoIBAQCoIvUdNWbUf5v0
uym+KXJuhByBFJcv4nkyjbXO5CLsbyNGevtHKsMUrBnUOJEnUvn/ChDTilcA9rtC
sAxjy5HKHlJtZGtvmQhIO/Q4JXbzIlxHPA/xczleNNvGLln2iE9LM+o4cHMWBHOi
GITsKgAvvhUqMa8YGXU+esyjs8jo51fyo/XdqUE3iLjm3+d2ZZnC8vjpKdVOIfGd
C/8TLXDFkj1MawRVEBgV06Mqnjxg9pPEwcuxYhTWXxwKXjLCoJ2NmXx4k5+6vsDC
5mqnb0/aOIMX6ZCmkTWS6Sn9HuvVJ3Qvmb3FVh8gk4WY2xS7//emS9hXsrj9qZez
AJxP+Qp9AoGBAO5oK523lRR4yqRqHxEME6aRxKzG74zWVMYKS0SpY8lU7YQDe8AW
xwN4c4FPnxIplm/XNT/EUA8bQ9lZBY8rasTB6nYegujwbITk/QIT7CHdUxmZ4keW
3K18Yn3RZjYJrxAlE5JB756kISsz+rauguYMDi9enLA8hbXd2NBicIbLAoGBAM/9
UxEkgY2+/Eltz0idDSDWpD/DiXgNV8YE7fmZQkE9FZDjnmwGPpfyKjX+gN1dvmk8
qjBsJ6u5DkzyA30IyzUVxaz6Qq0/z1SHzNABoN1leB4ASxruMFSoaeWkb17dKAQ/
KDC/U447VVI5fDatrqsFB1F0CO470KQukTaiouqXAoGBAObKGycD+CqkzS7auJZV
HZTLWhx0PKQXPFu2zWR7omjdeUyp3pt2sVOvwAk3XeNENSixqg+/6EyndUgrwJD3
U9WDb4jHQq1jSXpg/niLdrTVv8Nxz7bD2X9sgSARnSPEvh8f9VFJ2UC23JEpMZS1
XWx70SOUMJT/EeWcDG62TP5/AoGAOsnDnOjQpZwB+09Kc5/QgiOpMUy3onNDB/mE
ujQTghP8gIOV17q8Hn6YZ8KT8f35QA2hnSY04FjiLeWKDuFZbpvEz+u8xPNwSthH
j9OmAG4Z0YELuYTxrDweEoaz5ABmuyyO05iAqYcjyqXs8heNc1FsjB1cGNpXUtDG
wsadfekCgYEAlVvU+StUkNU1bHaKUXc/pa1jCIWkd2a9Hi4/Wibd8YzWF3TZ2hdi
kSWZOLGi8nCYqNDu+oN0t5ItS9bFvuy+cUkuimK0GgMksAwumXSIRx064oguXKGl
FIa62JUQcV2v55xrtLYX+7emRwzq6zUPU9pkn9rqgK44xU8HDvcKgSM=
-----END RSA PRIVATE KEY-----
";

License

MIT

You might also like...
新しい IMAP client in Rust

新しい IMAP client 新しい (atarashii/new) IMAP client in Rust. It supports plain and secure connections. In progress It's under development... Usage Put thi

Implementation of mjml in rust

MRML Introduction This project is a reimplementation of the nice MJML markup language in Rust. How to use it use mrml; fn main() { match mrml::to

Rust implementation of catapulte email sender
Rust implementation of catapulte email sender

Catapulte What is catapulte? Catapulte is an open source mailer you can host yourself. You can use it to quickly catapult your transactionnal emails t

DNS Server written in Rust for fun, see https://dev.to/xfbs/writing-a-dns-server-in-rust-1gpn

DNS Fun Ever wondered how you can write a DNS server in Rust? No? Well, too bad, I'm telling you anyways. But don't worry, this is going to be a fun o

Dav-server-rs - Rust WebDAV server library. A fork of the webdav-handler crate.

dav-server-rs A fork of the webdav-handler-rs project. Generic async HTTP/Webdav handler Webdav (RFC4918) is defined as HTTP (GET/HEAD/PUT/DELETE) plu

A simple web server(and library) to display server stats over HTTP and Websockets/SSE or stream it to other systems.

x-server-stats A simple web server(and library) to display server stats over HTTP and Websockets/SSE or stream it to other systems. x-server(in x-serv

axum-server is a hyper server implementation designed to be used with axum framework.

axum-server axum-server is a hyper server implementation designed to be used with axum framework. Features Conveniently bind to any number of addresse

Jex Compiler Server - Server that runs Jex code

Server that compiles and runs Jex code.

AIDL Language Server Protocol (LSP) server

AIDL Language Server Protocol (LSP) server Experimental AIDL LSP server based on rust-aidl-parser. Features: diagnostics workspace symbols (Ctrl+T in

Solana Game Server is a decentralized game server running on Solana, designed for game developers

Solana Game Server* is the first decentralized Game Server (aka web3 game server) designed for game devs. (Think web3 SDK for game developers as a ser

Live Server - Launch a local network server with live reload feature for static pages

Live Server - Launch a local network server with live reload feature for static pages

A LSP (Language Server Protocol) server for OpenSCAD.

openscad-LSP A LSP (Language Server Protocol) server for OpenSCAD. inspired by dzhu/openscad-language-server Tested with VSCode on Mac and Windows. Te

Static Web Server - a very small and fast production-ready web server suitable to serve static web files or assets
Static Web Server - a very small and fast production-ready web server suitable to serve static web files or assets

Static Web Server (or SWS abbreviated) is a very small and fast production-ready web server suitable to serve static web files or assets.

impl LSP (Language Server Protocol) Server for librime

rime-ls 为 rime 输入法核心库 librime (的部分功能) 实现 LSP 协议, 从而通过编辑器的代码补全功能输入汉字. 项目还处在早期阶段, 各方面都非常不成熟. 目标是提供 rime + LSP 的通用解决方案, 在不同编辑器内实现与其他 rime 前端类似的输入体验. Feat

Bruteforce connecting to a specific Sea of Thieves server. Useful if you want to be in the same server as your friends.

SoT Server Finder Find which Sea of Thieves server you're connected to. Useful if you want to be in the same server as your friends. Setup Download so

QUIC proxy that allows to use QUIC to connect to an SSH server without needing to patch the client or the server.

quicssh-rs 😄 quicssh-rs is a QUIC proxy that allows to use QUIC to connect to an SSH server without needing to patch the client or the server. quicss

Leptos server signals synced through Server-Sent-Events (SSE)

Leptos Server Sent Events Server signals are leptos signals kept in sync with the server through server-sent-events (SSE). The signals are read-only o

General purpose client/server networking library written in Rust, built on top of the QUIC protocol which is implemented by quinn

Overview "This library stinks!" ... "Unless you like durian" durian is a client-server networking library built on top of the QUIC protocol which is i

A client and server implementation of the OPC UA specification written in Rust

Introduction This is an OPC UA server / client API implementation for Rust. Linux Windows OPC UA is an industry standard for monitoring of data. It's

Owner
DUNEF
DUNEF
mail-builder is a flexible e-mail builder library written in Rust that generates RFC5322 compliant e-mail messages

mail-builder mail-builder is a flexible e-mail builder library written in Rust that generates RFC5322 compliant e-mail messages. The library has full

Stalwart Labs 37 Dec 19, 2022
📫Himalaya: CLI email client written in Rust.

??Himalaya: CLI email client written in Rust.

Clément DOUIN 2.1k Jan 7, 2023
Check if an email address exists without sending any email, written in Rust.

Check if an email address exists without sending any email, written in Rust.

Reacher 3.5k Dec 31, 2022
A mail suite written in rust meant to be easy to use.

Erooster A mail suite written in rust meant to be easy to use. Getting started Currently the setup is quite rough. You need some certificates for your

Marcel 33 Dec 19, 2022
Unofficial Rust library for the SendGrid API

sendgrid-rs Unofficial Rust library for the SendGrid API. This crate requires Rust 1.15 or higher as it uses a crate that has a custom derive implemen

Garrett Squire 88 Dec 27, 2022
a mailer library for Rust

lettre A mailer library for Rust NOTE: this readme refers to the 0.10 version of lettre, which is still being worked on. The master branch and the alp

lettre 1.3k Jan 4, 2023
Rust library to parse mail files

mailparse A simple parser for MIME email messages. API The primary entry point for this library is the following function: parse_mail(&[u8]) -> Re

Kartikaya Gupta (kats) 150 Dec 27, 2022
Fast and robust e-mail parsing library for Rust

mail-parser mail-parser is an e-mail parsing library written in Rust that fully conforms to the Internet Message Format standard (RFC 5322), the Multi

Stalwart Labs 158 Jan 1, 2023
E-mail delivery library for Rust with DKIM support

mail-send mail-send is a Rust library to build, sign and send e-mail messages via SMTP. It includes the following features: Generates e-mail messages

Stalwart Labs 165 Oct 23, 2023
A small unofficial library to send emails using Sendgrid.

sendgrid_thin A thin wrapper around the SendGrid V3 API. It does not use the crate tokio or hyper and is therefore very lightweight and do not interfe

Reinaldo Rozato Junior 3 Nov 17, 2022