imap API reference
An IMAP4rev1 client (RFC 3501): tagged commands and untagged "*" responses over the net system library, with plaintext / implicit TLS / STARTTLS and auth by LOGIN, XOAUTH2, CRAM-MD5, or SCRAM-SHA-1 / SCRAM-SHA-256. A practical subset covering both reading and basic folder management, not the full protocol: SELECT a folder, SEARCH it (filtered), FETCH whole messages or named headers, and manage messages - STORE flags, COPY, CREATE a folder, mark + EXPUNGE (so, delete and move). It is not read-only. Retrieved messages come back as strings for the mime module to parse. Uses net, so it needs the default jennifer binary. A session is stateful: connect, selectFolder, search / fetch / fetchHeaders, optional addFlags / expunge, logout. A "NO" / "BAD" completion throws a catchable Error (kind "imap"). One fixed command tag is used, which is safe for this synchronous client (one command in flight at a time). Message literals ({N}) are framed over bytes by their byte count, so an 8-bit / multi-byte UTF-8 literal is read byte-exact.
Import with import "imap.j" as imap;. See the imap guide for prose and examples.
Functions
imap.addFlags(session as Session, uid as int, flags as string)
Add IMAP flags / keywords to a message (UID STORE +FLAGS.SILENT). Use a keyword like "$cl_1" to colour the message in Thunderbird, or the system flag "\\Deleted" to mark it for expunge. The folder is selected read-write by selectFolder, so the store is permitted.
Parameters
session{Session}- the open sessionuid{int}- the message UIDflags{string}- the space-separated flags, e.g. "$cl_1" or "\\Deleted"
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.append(session as Session, folder as string, message as string)
Upload message (a full RFC 5322 message - headers, a blank line, then the body, e.g. built with the mime module) into folder (APPEND). The folder must already exist. Use this to save a copy to "Sent" after sending, or to store a message a client composed. The message is sent as a byte-counted literal, so any 8-bit / multi-byte content is uploaded exactly.
Parameters
session{Session}- the open sessionfolder{string}- the destination folder namemessage{string}- the full RFC 5322 message
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap"), e.g. a missing folder
imap.appendWith(session as Session, folder as string, flags as string, message as string)
Like append, but sets initial flags on the stored message (APPEND with a flag list) - e.g. "\\Seen" for a copy to Sent (already read) or "\\Draft" for a saved draft. flags is a space-separated flag string, as addFlags.
Parameters
session{Session}- the open sessionfolder{string}- the destination folder nameflags{string}- the space-separated initial flags, e.g. "\\Seen"message{string}- the full RFC 5322 message
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.connect(opts as Options)
Open a session: greeting, optional STARTTLS, then LOGIN (or XOAUTH2).
Parameters
opts{Options}- the connection and auth parameters
Returns {Session} - the open session
Throws
{Error}- on a bad greeting or a "NO" / "BAD" login completion (kind "imap")
imap.copy(session as Session, uid as int, folder as string)
Copy a message (by UID) into another folder (UID COPY). The source copy stays until it is deleted, so the standard "move" is copy + addFlags(..., "\\Deleted") + expunge - or the atomic move below. The destination folder must already exist - COPY to a missing one is a "NO [TRYCREATE]" error.
Parameters
session{Session}- the open sessionuid{int}- the message UIDfolder{string}- the destination folder name
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.createFolder(session as Session, folder as string)
Create a folder (CREATE). A server answers "NO" if it already exists (often with an "[ALREADYEXISTS]" response code), so wrap this in try / catch for a create-if-missing.
Parameters
session{Session}- the open sessionfolder{string}- the folder name to create
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap"), including when the folder already exists
imap.criteria()
An empty Criteria (matches all messages). Set the fields you want to filter on, then pass it to search.
Returns {Criteria} - a zero-value criteria
imap.done(session as Session)
Leave IDLE: send DONE, drain any pending pushes, and read the tagged completion of the IDLE command, returning the session to command mode so ordinary calls (fetch, search, ...) work again. Pair every idle with a done.
Parameters
session{Session}- the idling session
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.expunge(session as Session)
Permanently remove every message flagged "\\Deleted" in the selected folder (EXPUNGE). Sequence numbers shift as messages are removed, so mark all the target messages with addFlags(..., "\\Deleted") first and call this once.
Parameters
session{Session}- the open session
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.fetch(session as Session, uid as int)
Retrieve a message (its full body) as a raw string for mime.parse, addressed by its stable UID (UID FETCH ... BODY.PEEK[]). Get a UID from search.
Parameters
session{Session}- the open sessionuid{int}- the message UID
Returns {string} - the raw message body
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.fetchAll(opts as Options, folder as string)
Connect, select folder, retrieve every message, and log out.
Parameters
opts{Options}- the connection and auth parametersfolder{string}- the folder name
Returns {list of string} - the raw body of every message
Throws
{Error}- on a bad greeting or a "NO" / "BAD" completion (kind "imap")
imap.fetchHeaders(session as Session, uid as int, fields as string)
Retrieve only the named header fields of a message (space-separated, e.g. "SUBJECT DATE") as a raw header block for mime.parse - far cheaper than fetching the whole body when you only need a few headers. Addressed by UID.
Parameters
session{Session}- the open sessionuid{int}- the message UIDfields{string}- the space-separated header names, e.g. "SUBJECT DATE"
Returns {string} - the raw header block (the fields plus the terminating blank line)
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.fetchMessage(session as Session, uid as int)
Fetch a message by UID and parse it into a mime.Part tree, ready for mime.attachments / mime.textBodies / mime.data. Convenience for the common mime.parse(imap.fetch(...)) pattern; import mime too to walk the result.
Parameters
session{Session}- the open sessionuid{int}- the message UID
Returns {mime.Part} - the parsed message tree
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.fetchPartial(session as Session, uid as int, offset as int, length as int)
Retrieve a byte range of a message's body (UID FETCH ... BODY.PEEK[]<offset.length>), so a large message can be pulled in bounded chunks instead of one huge literal. offset is the 0-based start, length the number of octets (the server returns fewer at end-of-body). Both must be >= 0.
Parameters
session{Session}- the open sessionuid{int}- the message UIDoffset{int}- the 0-based byte offset into the bodylength{int}- the number of octets to retrieve
Returns {string} - the requested byte range of the body
Throws
{Error}- kind "imap" on a bad range or a "NO" / "BAD" completion
imap.flags(session as Session, uid as int)
Return the flags currently set on a message (by UID) as a space-separated string (e.g. "\\Seen $cl_1"), or "" when none. Useful to confirm a STORE actually persisted: a server that does not allow a custom keyword answers OK but drops it, so the keyword will be absent here.
Parameters
session{Session}- the open sessionuid{int}- the message UID
Returns {string} - the flags, space-separated (no surrounding parentheses)
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.folders(session as Session, pattern as string)
List the folders matching pattern (the IMAP LIST "" pattern). Use "*" for every folder at any depth, "%" for the top level only, or scope to a subtree with a prefix (all of Archive is the pattern Archive/ then *). The reference is empty, so put any prefix in the pattern. (Named folders, not list, since list is a reserved type keyword.)
Parameters
session{Session}- the open sessionpattern{string}- the folder pattern (*= any depth,%= one level)
Returns {list of Folder} - the matching folders (name, delimiter, flags)
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.idle(session as Session)
Enter IDLE (RFC 2177): send IDLE and wait for the server's "+ idling" continuation, leaving the session in the idling state so the server pushes mailbox changes (call supportsIdle first - a server that lacks the extension answers with a tagged "NO"/"BAD", surfaced here as an Error). Follow with receiveNotification / pollNotification to read pushes, then done to return to command mode. A server drops an idle session after about 29 minutes, so a long-lived client must periodically done and idle again.
Parameters
session{Session}- the open session
Throws
{Error}- kind "imap" when the server does not enter IDLE (e.g. a tagged "NO"/"BAD")
imap.logout(session as Session)
End the session and close the connection.
Parameters
session{Session}- the open session
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.move(session as Session, uid as int, folder as string)
Move a message (by UID) into another folder in one atomic step (UID MOVE, RFC 6851) - the server copies then removes and expunges the source, so unlike the copy + \\Deleted + expunge dance there is no window with a duplicate and no manual expunge. The destination folder must already exist. MOVE is widely but not universally supported; a server lacking it answers "BAD", so fall back to copy + addFlags(..., "\\Deleted") + expunge when catching that.
Parameters
session{Session}- the open sessionuid{int}- the message UIDfolder{string}- the destination folder name
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap"), including an unsupported MOVE
imap.pollNotification(session as Session, timeoutMs as int)
Like receiveNotification, but wait at most timeoutMs milliseconds (armed with net.setDeadline): return the next push if one arrives in time, else the empty sentinel (kind == "") so a poll loop can do other work between checks. Requires an idle first.
Parameters
session{Session}- the idling sessiontimeoutMs{int}- the maximum time to wait, in milliseconds
Returns {Notification} - the next push, or the empty sentinel on a timeout
Throws
{Error}- kind "imap" on a read failure other than a timeout
imap.receiveNotification(session as Session)
Block until the next server push arrives while idling and return it as a typed Notification ("exists" new-mail / "expunge" / "recent"). Returns the empty sentinel (kind == "") only if the peer closes or the server ends IDLE. No callbacks - wrap this in a loop, and in a spawn to run it beside other work. Requires an idle first.
Parameters
session{Session}- the idling session
Returns {Notification} - the next push, or the empty sentinel when IDLE ends
Throws
{Error}- kind "imap" on a read failure other than a timeout
imap.removeFlags(session as Session, uid as int, flags as string)
Remove IMAP flags / keywords from a message (UID STORE -FLAGS.SILENT), the inverse of addFlags. Removing a flag that is not set is a harmless no-op, e.g. removeFlags(session, uid, "$cl_1") clears that tag, "\\Deleted" un-marks a pending delete.
Parameters
session{Session}- the open sessionuid{int}- the message UIDflags{string}- the space-separated flags to clear, e.g. "$cl_1"
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.search(session as Session, criteria as Criteria)
Return the UIDs of the messages in the selected folder matching criteria (UID SEARCH). A UID is stable across an expunge - it keeps addressing the same message after others are removed - so the returned ids are the correct input to every other verb here and the basis for "fetch only what is new since last run". The server-side fields become one round-trip (no bodies); if any client-side condition is set - a subjectRegex / fromRegex, hasAttachments, or a since / before carrying a time-of-day - the candidates are then refined by fetching just their headers / structure / INTERNALDATE. An empty imap.criteria() returns every message (UID SEARCH ALL).
Parameters
session{Session}- the open sessioncriteria{Criteria}- the filter (build withimap.criteria()+ fields)
Returns {list of int} - the matching message UIDs
Throws
{Error}- kind "imap" on a "NO" / "BAD" completion, an inverted date range (sinceafterbefore), or a control character in a substring field
imap.selectFolder(session as Session, name as string)
Select a folder (e.g. "INBOX") and return its message count.
Parameters
session{Session}- the open sessionname{string}- the folder name
Returns {int} - the number of messages in the folder
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.status(session as Session, folder as string)
Query a folder's counts without selecting it (STATUS) - message / unseen / recent totals plus UIDNEXT / UIDVALIDITY. Handy for a folder badge or a "new mail?" poll on a folder other than the selected one.
Parameters
session{Session}- the open sessionfolder{string}- the folder name
Returns {Status} - the folder counts
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
imap.supportsIdle(session as Session)
Report whether the server advertises the IDLE extension (RFC 2177) in its CAPABILITY reply. Call before idle: a server without IDLE answers the IDLE command with a tagged "NO"/"BAD", which idle surfaces as an Error.
Parameters
session{Session}- the open session
Returns {bool} - true when IDLE is advertised
Throws
{Error}- on a "NO" / "BAD" completion (kind "imap")
Structs
imap.Criteria
A message-search filter. Every field is optional; a zero-value Criteria (from imap.criteria()) matches all messages, so search($s, imap.criteria()) is the old SEARCH ALL. Fields split into two groups by where they run:
| Field | Type | Description |
|---|---|---|
subject | string | SUBJECT substring ("" ignores it) |
from | string | FROM substring |
to | string | TO substring |
text | string | TEXT substring (whole message) |
since | time.Time | only messages on/after this instant (a zero time ignores it) |
before | time.Time | only messages strictly before this instant (a zero time ignores it) |
seen | bool | only \Seen messages |
unseen | bool | only unseen messages |
flagged | bool | only \Flagged messages |
answered | bool | only \Answered messages |
largerThan | int | LARGER than n bytes (0 ignores it) |
smallerThan | int | SMALLER than n bytes (0 ignores it) |
subjectRegex | string | RE2 pattern on the Subject header (client-side, "" ignores it) |
fromRegex | string | RE2 pattern on the From header (client-side) |
hasAttachments | bool | keep only messages with an attachment (client-side heuristic) |
imap.Folder
One folder as reported by list.
| Field | Type | Description |
|---|---|---|
name | string | the folder name (ASCII, or modified-UTF-7 for non-ASCII names) |
delimiter | string | the hierarchy separator (e.g. "/" or "."), "" for a flat namespace |
flags | list of string | the folder attribute flags (e.g. "\HasChildren", "\Noselect") |
imap.Notification
One server-pushed mailbox change delivered during IDLE (RFC 2177). A kind of "" is the idle-gap sentinel (imap.pollNotification timed out, or IDLE ended) carrying no push.
| Field | Type | Description |
|---|---|---|
kind | string | the push kind: "exists" (new message count), "expunge" (a message was removed), "recent" (recent-count change), or "" (no push / idle gap) |
number | int | the sequence number or count the push carried (0 for the sentinel) |
imap.Options
The parameters for opening an IMAP session.
| Field | Type | Description |
|---|---|---|
host | string | the server hostname |
port | int | the server port (e.g. 993 for implicit TLS) |
security | transport.Security | the transport: transport.Security.None (plaintext) / .Tls (implicit) / .Starttls |
user | string | the login username |
pass | string | the login password, or the OAuth2 access token when auth is "xoauth2" |
auth | string | the auth mechanism: "" (default - LOGIN), "auto" (probe CAPABILITY and pick the strongest mechanism, falling back to LOGIN), "xoauth2", "cram" (CRAM-MD5), "scram-sha-1", or "scram-sha-256" |
imap.Session
An open IMAP session.
| Field | Type | Description |
|---|---|---|
conn | net.Conn | the underlying connection |
imap.Status
Folder status counts, as reported by status. A field the server did not return stays 0.
| Field | Type | Description |
|---|---|---|
messages | int | total messages (MESSAGES) |
recent | int | messages with the \Recent flag (RECENT) |
unseen | int | messages without \Seen (UNSEEN) |
uidnext | int | the UID that will be assigned to the next message (UIDNEXT) |
uidvalidity | int | the folder's UID-validity value (UIDVALIDITY) |