LumenMail
A Gleam library for sending emails via SMTP, inspired by the Rust mail-send crate.

Features
- Direct SMTP connections with TLS/SSL support
- STARTTLS and implicit TLS (port 465)
- Multiple authentication mechanisms (PLAIN, LOGIN, CRAM-MD5, XOAUTH2)
- HTML and plain text emails
- File attachments and inline images
- Custom headers
- Email threading (In-Reply-To, References)
- RFC 5322 compliant message formatting
- Connection reuse for sending multiple emails
Installation
Add lumenmail to your Gleam project:
gleam add lumenmail
Quick Start
import lumenmail/message
import lumenmail/smtp
pub fn main() {
// Build the email message
let email = message.new()
|> message.from_name_email("John Doe", "john@example.com")
|> message.to_email("recipient@example.com")
|> message.subject("Hello from Gleam!")
|> message.text_body("This is a test email sent with lumenmail.")
// Connect to SMTP server and send
let assert Ok(client) = smtp.builder("smtp.example.com", 587)
|> smtp.auth("username", "password")
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
Examples
Simple Text Email
import lumenmail/message
import lumenmail/smtp
pub fn send_simple_email() {
let email = message.new()
|> message.from_email("sender@example.com")
|> message.to_email("recipient@example.com")
|> message.subject("Hello!")
|> message.text_body("This is a plain text email.")
let assert Ok(client) = smtp.builder("smtp.example.com", 587)
|> smtp.auth("user", "password")
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
HTML Email with Plain Text Fallback
import lumenmail/message
import lumenmail/smtp
pub fn send_html_email() {
let email = message.new()
|> message.from_name_email("Newsletter", "news@example.com")
|> message.to_email("subscriber@example.com")
|> message.subject("Weekly Newsletter")
|> message.text_body("Your weekly update in plain text.")
|> message.html_body("<h1>Weekly Newsletter</h1><p>Your weekly update!</p>")
let assert Ok(client) = smtp.builder("smtp.example.com", 587)
|> smtp.auth("user", "password")
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
Email with Attachments
import lumenmail/message
import lumenmail/smtp
pub fn send_with_attachment() {
let pdf_data = <<...>> // Your PDF file as BitArray
let email = message.new()
|> message.from_email("sender@example.com")
|> message.to_email("recipient@example.com")
|> message.subject("Document Attached")
|> message.text_body("Please find the document attached.")
|> message.attachment("document.pdf", message.ApplicationOctetStream, pdf_data)
let assert Ok(client) = smtp.builder("smtp.example.com", 587)
|> smtp.auth("user", "password")
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
Multiple Recipients (To, CC, BCC)
import lumenmail/message
import lumenmail/smtp
pub fn send_to_multiple() {
let email = message.new()
|> message.from_email("sender@example.com")
|> message.to_email("primary@example.com")
|> message.to_name_email("Jane Doe", "jane@example.com")
|> message.cc_email("cc@example.com")
|> message.bcc_email("bcc@example.com")
|> message.subject("Team Update")
|> message.text_body("Hello team!")
let assert Ok(client) = smtp.builder("smtp.example.com", 587)
|> smtp.auth("user", "password")
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
Using Gmail with App Password
import lumenmail/message
import lumenmail/smtp
pub fn send_via_gmail() {
let email = message.new()
|> message.from_email("your.email@gmail.com")
|> message.to_email("recipient@example.com")
|> message.subject("Sent from Gmail")
|> message.text_body("Hello from Gmail!")
// Gmail uses port 587 with STARTTLS
let assert Ok(client) = smtp.builder("smtp.gmail.com", 587)
|> smtp.auth("your.email@gmail.com", "your-app-password")
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
Using Implicit TLS (Port 465)
import lumenmail/message
import lumenmail/smtp
pub fn send_with_implicit_tls() {
let email = message.new()
|> message.from_email("sender@example.com")
|> message.to_email("recipient@example.com")
|> message.subject("Secure Email")
|> message.text_body("Sent over implicit TLS.")
// Port 465 automatically uses implicit TLS
let assert Ok(client) = smtp.builder("smtp.example.com", 465)
|> smtp.auth("user", "password")
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
OAuth2 Authentication
import lumenmail/message
import lumenmail/smtp
import lumenmail/types
pub fn send_with_oauth2() {
let email = message.new()
|> message.from_email("user@gmail.com")
|> message.to_email("recipient@example.com")
|> message.subject("OAuth2 Email")
|> message.text_body("Sent using OAuth2!")
let assert Ok(client) = smtp.builder("smtp.gmail.com", 587)
|> smtp.credentials(types.OAuth2("user@gmail.com", "oauth2-access-token"))
|> smtp.connect()
let assert Ok(_) = smtp.send(client, email)
let assert Ok(_) = smtp.close(client)
}
Sending Multiple Emails (Connection Reuse)
import lumenmail/message
import lumenmail/smtp
pub fn send_multiple_emails() {
let assert Ok(client) = smtp.builder("smtp.example.com", 587)
|> smtp.auth("user", "password")
|> smtp.connect()
// Send first email
let email1 = message.new()
|> message.from_email("sender@example.com")
|> message.to_email("recipient1@example.com")
|> message.subject("Email 1")
|> message.text_body("First email")
let assert Ok(_) = smtp.send(client, email1)
// Reset connection state
let assert Ok(_) = smtp.reset(client)
// Send second email
let email2 = message.new()
|> message.from_email("sender@example.com")
|> message.to_email("recipient2@example.com")
|> message.subject("Email 2")
|> message.text_body("Second email")
let assert Ok(_) = smtp.send(client, email2)
let assert Ok(_) = smtp.close(client)
}
API Reference
Message Builder
| Function | Description |
message.new() | Create a new empty message |
message.from_email(msg, email) | Set sender email |
message.from_name_email(msg, name, email) | Set sender with display name |
message.to_email(msg, email) | Add To recipient |
message.to_name_email(msg, name, email) | Add To recipient with name |
message.cc_email(msg, email) | Add CC recipient |
message.bcc_email(msg, email) | Add BCC recipient |
message.subject(msg, subject) | Set subject line |
message.text_body(msg, text) | Set plain text body |
message.html_body(msg, html) | Set HTML body |
message.attachment(msg, filename, content_type, data) | Add file attachment |
message.inline_attachment(msg, filename, content_type, data, content_id) | Add inline attachment |
message.header(msg, name, value) | Add custom header |
message.priority(msg, priority) | Set email priority |
message.reply_to(msg, address) | Set Reply-To address |
SMTP Client Builder
| Function | Description |
smtp.builder(host, port) | Create SMTP client builder |
smtp.auth(builder, username, password) | Set authentication credentials |
smtp.credentials(builder, creds) | Set credentials (Plain or OAuth2) |
smtp.implicit_tls(builder, enabled) | Enable/disable implicit TLS |
smtp.timeout(builder, ms) | Set connection timeout |
smtp.helo_host(builder, hostname) | Set HELO hostname |
smtp.allow_invalid_certs(builder, allow) | Allow invalid TLS certs (testing only) |
smtp.connect(builder) | Connect to SMTP server |
SMTP Client Operations
| Function | Description |
smtp.send(client, message) | Send an email message |
smtp.send_raw(client, from, to, data) | Send raw email data |
smtp.reset(client) | Reset connection for next email |
smtp.noop(client) | Send NOOP to keep connection alive |
smtp.close(client) | Close the connection |
smtp.capabilities(client) | Get server capabilities |
Common SMTP Ports
| Port | Protocol | Description |
| 25 | SMTP | Standard SMTP (often blocked by ISPs) |
| 587 | Submission | SMTP with STARTTLS (recommended) |
| 465 | SMTPS | SMTP over implicit TLS |
Development
gleam test # Run the tests
gleam build # Build the project
License
Apache-2.0 OR MIT