Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mailman 📰

A microservice you can use to send emails on your behalf, written in Rust on top of lettre and axum.

Mailman follows the same pattern as geodude:

  • an HTTP microservice that owns your SMTP credentials and sends mail, and
  • a Rust client crate (SDK) that other services use to ask it to send mail.

The pattern is: deploy one mailman server somewhere, then cargo add mailman in every other Rust service that needs to send email. The server keeps the SMTP credentials in one place; clients just make HTTP calls.

Client usage (SDK)

[dependencies]
mailman = "0.2"  # client by default; no need to disable anything
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Point at your server once and send mail from anywhere:

mailman::setup("https://mailman.example.com", "super-secret-token")?;

let email = mailman::SendEmailRequest::new(
    "Hello from mailman",
    "This one's plain text.",
    ["alice@example.com", "Bob <bob@example.com>"],
);
mailman::send(&email).await?;

Or let it read the URL and token from the environment (MAILMAN_URL and MAILMAN_API_TOKEN):

let email = mailman::SendEmailRequest::new("Hi", "<h1>Hello!</h1>", ["bob@example.com"])
    .html(true)
    .cc(["carol@example.com"])
    .attach(mailman::Attachment::new(
        "invoice.pdf",
        "application/pdf",
        base64_encoded_bytes,
    ));

mailman::send(&email).await?; // reads MAILMAN_URL / MAILMAN_API_TOKEN

send lazily falls back to MAILMAN_URL / MAILMAN_API_TOKEN on first call, so for the env-only path you can skip setup() entirely. For multiple servers or finer control over the reqwest::Client, use mailman::Client::new(url, token) directly.

API

POST /send

Bearer-token authentication is enabled by default. Send the shared secret from MAILMAN_API_TOKEN as Authorization: Bearer <token>. Set MAILMAN_REQUIRE_API_TOKEN=false to make the endpoint unauthenticated.

Request body:

{
  "subject": "Hello from mailman",
  "body": "<h1>Hi!</h1><p>This one is HTML.</p>",
  "html": true,
  "to": ["alice@example.com", "Bob <bob@example.com>"],
  "cc": ["carol@example.com"],
  "from": "Mailman <noreply@example.com>",
  "attachments": [
    {
      "filename": "invoice.pdf",
      "content_type": "application/pdf",
      "content": "JVBERi0xLjQKJ..."
    }
  ]
}
Field Required Notes
subject yes The subject line.
body yes The message body. Plain text unless html is true.
to yes One or more recipients. Bare (a@b.com) or named (Name <a@b.com>).
cc no Carbon-copy recipients. Defaults to empty.
from no Overrides the server's SMTP_FROM for this message.
html no Send body as text/html instead of text/plain. Defaults to false.
attachments no Files to attach. content is the file's bytes, base64-encoded.

Example:

curl -X POST http://localhost:8080/send \
  -H "Authorization: Bearer $MAILMAN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Hello",
    "body": "Sent by mailman.",
    "to": ["alice@example.com", "bob@example.com"]
  }'

Success returns 200:

{ "status": "sent", "message": "delivered to 2 recipient(s)" }

Errors return { "error": "..." } with an appropriate status (400 for bad input, 401 for a bad/missing token, 502 if the upstream SMTP send fails).

GET /health

Unauthenticated liveness check; returns ok.

API documentation

When the server is running, interactive Swagger documentation is available at http://localhost:8080/swagger-ui/. The raw OpenAPI document is served at http://localhost:8080/api-docs/openapi.json.

Use Swagger's Authorize button to enter MAILMAN_API_TOKEN when token authentication is enabled.

Configuration

All configuration is via environment variables. For local development, copy them into a .env file — dev.sh generates a template on first run, and .env is gitignored so credentials never get committed.

Variable Required Default Purpose
MAILMAN_REQUIRE_API_TOKEN no true Whether /send requires bearer-token authentication.
MAILMAN_API_TOKEN conditional Shared secret; required when MAILMAN_REQUIRE_API_TOKEN is true.
SMTP_HOST yes SMTP server hostname.
SMTP_PORT no 587 SMTP server port.
SMTP_USERNAME no SMTP username. Omit (with SMTP_PASSWORD) to send unauthenticated.
SMTP_PASSWORD no SMTP password.
SMTP_FROM no* Default From address. *Required unless every request supplies from.
SMTP_ENCRYPTION no starttls starttls (587), tls (465), or none (local relay only).
PORT no 8080 Port the HTTP server listens on.

Cargo features

Feature Default Purpose
client yes Builds the HTTP client SDK (reqwest + serde_json).
server no Builds the mailman server binary; pulls in axum, tokio, tracing, dotenvy, lettre.

Run the server locally with cargo run --no-default-features --features server. The Dockerfile and dev.sh already pass these flags.

Development mode

./dev.sh

This generates a .env template (if missing) and runs the server under cargo watch, rebuilding on changes.

Building

cargo build --release --no-default-features --features server

Deploying

Mailman ships as a Docker image. Build it and set the environment variables above in your platform's variables/secrets configuration:

docker build -t mailman .
docker run -p 8080:8080 --env-file .env mailman

Publishing the SDK

The client SDK is published to crates.io so other services can cargo add mailman. See PUBLISHING.md for the full release checklist; in short, push a v* tag and the release workflow runs cargo publish for you (it needs a CARGO_REGISTRY_TOKEN repository secret).

About

A microservice you can use to send emails on your behalf.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages