LumenMail

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

Package Version Hex Docs

Features

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

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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

PortProtocolDescription
25SMTPStandard SMTP (often blocked by ISPs)
587SubmissionSMTP with STARTTLS (recommended)
465SMTPSSMTP over implicit TLS

Development

gleam test  # Run the tests
gleam build # Build the project

License

Apache-2.0 OR MIT

Search Document