Skip to content

A logged-in IMAP or POP3 mailbox with one open folder. List, search and poll messages, and save their attachments to the project. The mailbox is read-only: nothing is deleted, moved or marked as read, so the same messages are found again by the next search. Messages are downloaded when you list or search them, so use search criteria and limit on large folders. The connection is re-established automatically if it was dropped, also after close().

How to get one

  1. Mail .imap()
  2. Mailbox
local mailbox = Mail.imap(list)

Functions

  • folder self Opens another folder of the mailbox (for example "Archive" or "INBOX/Invoices"; the separator depends on the server).
  • refresh self Closes and reopens the current folder so that messages that arrived since it was opened become visible.
  • messages MailMessage[] Returns every message in the current folder, newest first.
  • get MailMessage Returns the message with the given sequence number (the number field of a message, 1 is the oldest).
  • search MailMessage[] Returns the messages of the current folder that match all given criteria, newest first (see MailSearch for the keys).
  • poll MailMessage[] Waits for mail: reopens the folder and searches it every interval until at least one message matches, then returns the matches (newest first).
  • downloadAttachments table Saves all attachments of one message into a directory of the project and returns the saved file paths (relative to the project, as a list).
  • close self Closes the open folder and the connection to the server.
mailbox.folder(name: string)→self

Opens another folder of the mailbox (for example "Archive" or "INBOX/Invoices"; the separator depends on the server). Raises an error when the folder does not exist, or for any folder other than INBOX on POP3. Later calls read from this folder, and message numbers refer to it.

Parameters

name string
Folder name as the server knows it, case-sensitive on most servers

Returns

self chainable The mailbox itself, so calls can be chained.
mailbox.refresh()→self

Closes and reopens the current folder so that messages that arrived since it was opened become visible. poll does this by itself on every round; use refresh before messages, search or get when you wait for mail some other way.

Returns

self chainable The mailbox itself, so calls can be chained.
mailbox.messages()→MailMessage[]

Returns every message in the current folder, newest first. All messages are downloaded and parsed, which is slow for a large folder; use search with a limit instead.

Returns

MailMessage[] A message read from a mailbox.
Fields of MailMessage
number number
sequence number in the folder (1 is the oldest message); use it with Mailbox.get and Mailbox.downloadAttachments. It can change when messages are added or removed, so do not store it
folder string
name of the folder the message was read from
subject string
subject line, decoded
message_id string
value of the Message-ID header
from any[]
sender addresses, each as the server gives it: a display name followed by the address in angle brackets, or just the address (alice@example.com)
to any[]
recipients in the To header, formatted like from
cc any[]
recipients in the Cc header
bcc any[]
recipients in the Bcc header; normally empty because senders' Bcc headers are not kept
reply_to any[]
addresses replies should go to
sent_at number
Date header as a Unix timestamp in milliseconds, 0 when missing
sent_at_iso string
Date header as an ISO 8601 UTC time such as 2025-03-01T08:30:00Z, empty when missing
received_at number
time the server received the message, in Unix milliseconds, 0 when unknown
received_at_iso string
time the server received the message as ISO 8601 UTC, empty when unknown
size number
message size in bytes as reported by the server, 0 when unknown
content_type string
MIME type of the whole message, for example multipart/mixed; boundary=...
body string
the plain text of the message; when it has no plain text part, the HTML source of the HTML part (not converted to text)
text string
all text/plain parts joined, empty when there is none
html string
all text/html parts joined, empty when there is none
attachments MailAttachment[]
metadata of the attachments (use Mailbox.downloadAttachments to save the files)
has_attachments boolean
true when attachments is not empty
seen boolean
the message has been read (always false on POP3, which has no flags)
answered boolean
the message has been answered
deleted boolean
the message is marked as deleted
draft boolean
the message is a draft
flagged boolean
the message is flagged
recent boolean
the message arrived since the last session (IMAP only)
All table shapes
mailbox.get(number: number)→MailMessage

Returns the message with the given sequence number (the number field of a message, 1 is the oldest). The number can change when messages are added or removed, so use a number from a recent messages, search or poll result. Raises "No message found with number N" when it is out of range.

Parameters

number number
Sequence number from the number field of a message

Returns

MailMessage A message read from a mailbox.
Fields of MailMessage
number number
sequence number in the folder (1 is the oldest message); use it with Mailbox.get and Mailbox.downloadAttachments. It can change when messages are added or removed, so do not store it
folder string
name of the folder the message was read from
subject string
subject line, decoded
message_id string
value of the Message-ID header
from any[]
sender addresses, each as the server gives it: a display name followed by the address in angle brackets, or just the address (alice@example.com)
to any[]
recipients in the To header, formatted like from
cc any[]
recipients in the Cc header
bcc any[]
recipients in the Bcc header; normally empty because senders' Bcc headers are not kept
reply_to any[]
addresses replies should go to
sent_at number
Date header as a Unix timestamp in milliseconds, 0 when missing
sent_at_iso string
Date header as an ISO 8601 UTC time such as 2025-03-01T08:30:00Z, empty when missing
received_at number
time the server received the message, in Unix milliseconds, 0 when unknown
received_at_iso string
time the server received the message as ISO 8601 UTC, empty when unknown
size number
message size in bytes as reported by the server, 0 when unknown
content_type string
MIME type of the whole message, for example multipart/mixed; boundary=...
body string
the plain text of the message; when it has no plain text part, the HTML source of the HTML part (not converted to text)
text string
all text/plain parts joined, empty when there is none
html string
all text/html parts joined, empty when there is none
attachments MailAttachment[]
metadata of the attachments (use Mailbox.downloadAttachments to save the files)
has_attachments boolean
true when attachments is not empty
seen boolean
the message has been read (always false on POP3, which has no flags)
answered boolean
the message has been answered
deleted boolean
the message is marked as deleted
draft boolean
the message is a draft
flagged boolean
the message is flagged
recent boolean
the message arrived since the last session (IMAP only)
All table shapes
mailbox.search(criteria: MailSearch)→MailMessage[]

Returns the messages of the current folder that match all given criteria, newest first (see MailSearch for the keys). A plain string instead of a table searches the subject, sender, recipients, body and Message-ID for that text. Matching is done in the agent, case-insensitively, on text that contains the search term. Returns an empty list when nothing matches; it does not wait for new mail (use poll for that) and does not see mail that arrived after the folder was opened until you call refresh. Raises an error for a date in since or before that cannot be parsed.

Parameters

criteria MailSearch
Search criteria table, or a string to search in subject, sender, recipients, body and Message-ID

Returns

MailMessage[] A message read from a mailbox.
Fields of MailMessage
number number
sequence number in the folder (1 is the oldest message); use it with Mailbox.get and Mailbox.downloadAttachments. It can change when messages are added or removed, so do not store it
folder string
name of the folder the message was read from
subject string
subject line, decoded
message_id string
value of the Message-ID header
from any[]
sender addresses, each as the server gives it: a display name followed by the address in angle brackets, or just the address (alice@example.com)
to any[]
recipients in the To header, formatted like from
cc any[]
recipients in the Cc header
bcc any[]
recipients in the Bcc header; normally empty because senders' Bcc headers are not kept
reply_to any[]
addresses replies should go to
sent_at number
Date header as a Unix timestamp in milliseconds, 0 when missing
sent_at_iso string
Date header as an ISO 8601 UTC time such as 2025-03-01T08:30:00Z, empty when missing
received_at number
time the server received the message, in Unix milliseconds, 0 when unknown
received_at_iso string
time the server received the message as ISO 8601 UTC, empty when unknown
size number
message size in bytes as reported by the server, 0 when unknown
content_type string
MIME type of the whole message, for example multipart/mixed; boundary=...
body string
the plain text of the message; when it has no plain text part, the HTML source of the HTML part (not converted to text)
text string
all text/plain parts joined, empty when there is none
html string
all text/html parts joined, empty when there is none
attachments MailAttachment[]
metadata of the attachments (use Mailbox.downloadAttachments to save the files)
has_attachments boolean
true when attachments is not empty
seen boolean
the message has been read (always false on POP3, which has no flags)
answered boolean
the message has been answered
deleted boolean
the message is marked as deleted
draft boolean
the message is a draft
flagged boolean
the message is flagged
recent boolean
the message arrived since the last session (IMAP only)
All table shapes

Example

local invoices = mailbox.search({ subject = "invoice", hasAttachments = true, since = "2025-01-01", limit = 5 })
for _, message in ipairs(invoices) do
print(message.number, message.subject, message.sent_at_iso)
end
local byText = mailbox.search("password reset")
mailbox.poll()→MailMessage[]mailbox.poll(criteria: MailSearch)→MailMessage[]mailbox.poll(criteria: MailSearch, timeoutSeconds: number)→MailMessage[]mailbox.poll(criteria: MailSearch, timeoutSeconds: number, intervalMs: number)→MailMessage[]

Waits for mail: reopens the folder and searches it every interval until at least one message matches, then returns the matches (newest first). When the timeout passes without a match it returns an empty list and does not raise an error, so check the result. A timeout of 0 checks once. Defaults: 30 seconds timeout, 1000 ms interval. Messages that are already in the folder count too, so a poll for a subject that older mail also has returns immediately. Make the criteria specific (a unique subject or code, or since = <time you sent the request>). With a single number as argument, poll(60) waits up to 60 seconds for any message.

Parameters

criteria MailSearch optional
Criteria with the same keys as search (a table or string); a number here is read as the timeout in seconds
timeoutSeconds number optional
Seconds to keep trying; 0 checks once; default 30
intervalMs number optional
Milliseconds between checks, at least 1; default 1000

Returns

MailMessage[] A message read from a mailbox.
Fields of MailMessage
number number
sequence number in the folder (1 is the oldest message); use it with Mailbox.get and Mailbox.downloadAttachments. It can change when messages are added or removed, so do not store it
folder string
name of the folder the message was read from
subject string
subject line, decoded
message_id string
value of the Message-ID header
from any[]
sender addresses, each as the server gives it: a display name followed by the address in angle brackets, or just the address (alice@example.com)
to any[]
recipients in the To header, formatted like from
cc any[]
recipients in the Cc header
bcc any[]
recipients in the Bcc header; normally empty because senders' Bcc headers are not kept
reply_to any[]
addresses replies should go to
sent_at number
Date header as a Unix timestamp in milliseconds, 0 when missing
sent_at_iso string
Date header as an ISO 8601 UTC time such as 2025-03-01T08:30:00Z, empty when missing
received_at number
time the server received the message, in Unix milliseconds, 0 when unknown
received_at_iso string
time the server received the message as ISO 8601 UTC, empty when unknown
size number
message size in bytes as reported by the server, 0 when unknown
content_type string
MIME type of the whole message, for example multipart/mixed; boundary=...
body string
the plain text of the message; when it has no plain text part, the HTML source of the HTML part (not converted to text)
text string
all text/plain parts joined, empty when there is none
html string
all text/html parts joined, empty when there is none
attachments MailAttachment[]
metadata of the attachments (use Mailbox.downloadAttachments to save the files)
has_attachments boolean
true when attachments is not empty
seen boolean
the message has been read (always false on POP3, which has no flags)
answered boolean
the message has been answered
deleted boolean
the message is marked as deleted
draft boolean
the message is a draft
flagged boolean
the message is flagged
recent boolean
the message arrived since the last session (IMAP only)
All table shapes

Example

-- trigger the application to send a mail, then wait up to two minutes for it
local messages = mailbox.poll({ subject = "Your verification code", to = "test.user@example.com" }, 120, 2000)
Assert.equals(1, #messages)
local code = messages[1].body:match("(%d%d%d%d%d%d)")
mailbox.downloadAttachments(messageNumber: number, targetDir: string)→table

Saves all attachments of one message into a directory of the project and returns the saved file paths (relative to the project, as a list). The directory is created when needed. File names come from the message with characters that are not allowed in file names (\ / : * ? " < > |) replaced by _; when a file with that name exists, -1, -2, ... is added before the extension, so nothing is overwritten. Inline images of the HTML body are not saved. Returns an empty list when the message has no attachments; raises an error when the number is unknown or writing fails.

Parameters

messageNumber number
Sequence number from the number field of a message
targetDir string
Directory in the project to save the files in

Returns

table List of the saved file paths, relative to the project

Example

local messages = mailbox.search({ subject = "Monthly report", hasAttachments = true, limit = 1 })
local files = mailbox.downloadAttachments(messages[1].number, "downloads/mail")
for _, path in ipairs(files) do
print(path)
end
mailbox.close()→self

Closes the open folder and the connection to the server. Errors while closing are ignored. Calling another function afterwards connects again.

Returns

self chainable The mailbox itself, so calls can be chained.