work-in-progress
Rye has builtins for sending email via SMTP, reading email via IMAP, and parsing email files. This page covers the main operations.
Use the gomail-based builtins to compose and send emails via SMTP.
; Create a new email message
email-message
; Set headers
|Set-header "Subject" "Hello from Clojure"
|Set-header 'To "recipient@example.com"
|Set-header 'From "sender@example.com"
; Or use address header with display name
|Set-address-header "From" "sender@example.com" "John Doe"
|Set-address-header "To" "recipient@example.com" "Jane Smith"
; Set the body - plain text
|Set-body "text/plain" "Hello, this is the email body."
; Or HTML body
|Set-body "text/html" "<h1>Hello</h1><p>This is HTML content.</p>"
email-message
|Set-header "Subject" "Multipart email"
|Set-address-header "From" "sender@example.com" "Sender"
|Set-address-header "To" "recipient@example.com" "Recipient"
; Set plain text as primary body
|Set-body "text/plain" "Plain text version for simple clients."
; Add HTML as alternative
|Add-alternative "text/html" "<p>HTML version with <b>formatting</b>.</p>"
email-message :msg
|Set-header "Subject" "Email with attachment"
|Set-header 'To "recipient@example.com"
|Set-body "text/plain" "Please see the attached file."
; Attach a file
|Attach %file://document.pdf
|Attach %file://image.jpg
; Create SMTP dialer with server details
dialer: new-email-dialer "smtp.gmail.com" 587 "user@gmail.com" "app-password"
; Send the message
dialer .Dial-and-send msg
Parse email files (EML format) to extract content.
; Parse email from file
Reader %email.eml |Parse-email :eml
; Extract fields
print Subject? eml
print Text-body? eml
print Html-body? eml
print Message-id? eml
; main.clojure - parse email file from command line arg
file: first args
Reader file file |Parse-email :eml
print "Subject: " ++ Subject? eml
print "---"
print Text-body? eml
Run with: clojure main.clojure myemail.eml
Rye’s IMAP support is built on github.com/emersion/go-imap/v2/imapclient.
Open a connection with Open and an imap:// or imaps:// URI. The result is a
native value of kind imap-client; call generic methods on it with .Method.
; imaps:// uses implicit TLS (default port 993)
client: Open imaps://user:app-password@imap.example.com:993
; imap:// uses STARTTLS (default port 143).
; Port 993 automatically switches to implicit TLS.
client: Open imap://user:app-password@imap.example.com:993
; Keep the password out of the URI with Open\pwd
client: Open\pwd imaps://user@imap.example.com:993 "app-password"
; The path after the host selects a mailbox immediately
client: Open\pwd imaps://user@imap.example.com:993/INBOX "app-password"
; OAuth2 (XOAUTH2, with OAUTHBEARER fallback)
client: Open\oauth2 imaps://user@gmail.com@imap.gmail.com:993 "ya29.access-token"
Percent-encode reserved characters if the username or password is part of the
URI. Open\pwd avoids that for passwords.
; Get the list of available folders
folders: client .Get-folders
folders |for { .print }
; prints: INBOX, Sent, Drafts, etc.
; Select the inbox
client .Select-folder "INBOX"
; Or other folders
client .Select-folder "Sent"
client .Select-folder "Drafts"
Search-emails accepts IMAP search criteria and returns a block of matching UIDs.
; Unread messages
uids: client .Search-emails "UNSEEN"
; Combine criteria (multiple criteria are ANDed)
uids: client .Search-emails "FROM \"sender@example.com\" SUBJECT \"invoice\""
; Date searches use IMAP date format: DD-Mon-YYYY
uids: client .Search-emails "SINCE 01-Jan-2024"
uids: client .Search-emails "BEFORE 31-Dec-2024"
; Other common criteria
uids: client .Search-emails "NOT DELETED"
uids: client .Search-emails "LARGER 1048576"
uids: client .Search-emails "HEADER X-Mailer \"MyApp\""
; OR takes two search keys
uids: client .Search-emails "OR FROM \"alice@example.com\" FROM \"bob@example.com\""
Supported criteria include ALL, SEEN/UNSEEN, FLAGGED/UNFLAGGED,
ANSWERED, DELETED, DRAFT, RECENT, NEW, OLD, FROM, TO, CC,
BCC, SUBJECT, HEADER, BODY, TEXT, SINCE, BEFORE, ON, SENTSINCE,
SENTBEFORE, SENTON, LARGER, SMALLER, UID, KEYWORD, UNKEYWORD,
NOT, OR and parentheses.
Get headers only - useful when listing many messages:
uids: client .Search-emails "UNSEEN"
overviews: client .Get-overviews uids
; Each overview is a dict with: uid, subject, from, to, cc, bcc,
; date, received, message-id, size and flags
overviews |for { ::email
subject: email -> "subject"
from: email -> "from"
print "Subject: " ++ subject
print "From: " ++ from
print "---"
}
Get complete email content including bodies and attachments:
uids: client .Search-emails "UNSEEN"
emails: client .Get-emails uids
; Each email dict also has: sender, reply-to, text, html and attachments
emails |for { ::email
subject: email -> "subject"
from: email -> "from"
text: email -> "text"
print "Subject: " ++ subject
print "From: " ++ from
print "Text: " ++ text
print "---"
email -> "attachments" |for { ::attachment
name: attachment -> "name"
print "Attachment: " ++ name
}
}
Get-emails fetches messages with BODY.PEEK[], so it does not mark them as
read. Use Mark-seen when you want to mark a message as read.
Each attachment dict contains name, mime-type, size and content. The
content is exposed as a string because Rye has no separate binary type.
Mailbox-info returns information about the currently selected folder:
client .Select-folder "INBOX"
info: client .Mailbox-info
count: info -> "messages"
print join { "Messages in INBOX: " string count }
Get-raw-emails returns the complete RFC 822 source for each message. This is
useful for backups, MBOX export or feeding another parser/indexer:
uids: client .Search-emails "ALL"
raw-emails: client .Get-raw-emails uids
var 'uid 0
var 'raw ""
raw-emails |for { ::email
uid:: email -> "uid"
raw:: email -> "raw"
print join { "UID " string uid ": " string length? raw " bytes" }
}
For large mailboxes, fetch UIDs in chunks with split\every to avoid loading
everything into memory at once.
; Mark email as read
client .Mark-seen 12345
; Move email to another folder
client .Move-email 12345 "Archive"
; Delete email (marks it for deletion)
client .Delete-email 12345
; Permanently remove deleted emails
client .Expunge
client .Close
; Connect and process unread emails
client: Open\pwd imaps://user@imap.example.com:993 "app-password"
client .Select-folder "INBOX"
uids: client .Search-emails "UNSEEN"
either ( length? uids ) > 0 {
print join { "Found " string length? uids " unread emails" }
emails: client .Get-emails uids
emails |for { ::email
subject: email -> "subject"
from: email -> "from"
print "Subject: " ++ subject
print "From: " ++ from
print "---"
; Mark as read after processing
client .Mark-seen email -> "uid"
}
} {
print "No unread emails"
}
client .Close
The older positional constructor
imap-client "user" "password" "imap.example.com" 993 is still available for
compatibility, but the URI-based Open form above is preferred.