Skip to content
Jennifer Programming Language

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 session
  • uid {int} - the message UID
  • flags {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 session
  • folder {string} - the destination folder name
  • message {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 session
  • folder {string} - the destination folder name
  • flags {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 session
  • uid {int} - the message UID
  • folder {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 session
  • folder {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 session
  • uid {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 parameters
  • folder {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 session
  • uid {int} - the message UID
  • fields {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 session
  • uid {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 session
  • uid {int} - the message UID
  • offset {int} - the 0-based byte offset into the body
  • length {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 session
  • uid {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 session
  • pattern {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 session
  • uid {int} - the message UID
  • folder {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 session
  • timeoutMs {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 session
  • uid {int} - the message UID
  • flags {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 session
  • criteria {Criteria} - the filter (build with imap.criteria() + fields)

Returns {list of int} - the matching message UIDs

Throws

  • {Error} - kind "imap" on a "NO" / "BAD" completion, an inverted date range (since after before), 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 session
  • name {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 session
  • folder {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:

FieldTypeDescription
subjectstringSUBJECT substring ("" ignores it)
fromstringFROM substring
tostringTO substring
textstringTEXT substring (whole message)
sincetime.Timeonly messages on/after this instant (a zero time ignores it)
beforetime.Timeonly messages strictly before this instant (a zero time ignores it)
seenboolonly \Seen messages
unseenboolonly unseen messages
flaggedboolonly \Flagged messages
answeredboolonly \Answered messages
largerThanintLARGER than n bytes (0 ignores it)
smallerThanintSMALLER than n bytes (0 ignores it)
subjectRegexstringRE2 pattern on the Subject header (client-side, "" ignores it)
fromRegexstringRE2 pattern on the From header (client-side)
hasAttachmentsboolkeep only messages with an attachment (client-side heuristic)

imap.Folder

One folder as reported by list.

FieldTypeDescription
namestringthe folder name (ASCII, or modified-UTF-7 for non-ASCII names)
delimiterstringthe hierarchy separator (e.g. "/" or "."), "" for a flat namespace
flagslist of stringthe 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.

FieldTypeDescription
kindstringthe push kind: "exists" (new message count), "expunge" (a message was removed), "recent" (recent-count change), or "" (no push / idle gap)
numberintthe sequence number or count the push carried (0 for the sentinel)

imap.Options

The parameters for opening an IMAP session.

FieldTypeDescription
hoststringthe server hostname
portintthe server port (e.g. 993 for implicit TLS)
securitytransport.Securitythe transport: transport.Security.None (plaintext) / .Tls (implicit) / .Starttls
userstringthe login username
passstringthe login password, or the OAuth2 access token when auth is "xoauth2"
authstringthe 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.

FieldTypeDescription
connnet.Connthe underlying connection

imap.Status

Folder status counts, as reported by status. A field the server did not return stays 0.

FieldTypeDescription
messagesinttotal messages (MESSAGES)
recentintmessages with the \Recent flag (RECENT)
unseenintmessages without \Seen (UNSEEN)
uidnextintthe UID that will be assigned to the next message (UIDNEXT)
uidvalidityintthe folder's UID-validity value (UIDVALIDITY)