Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

mail.imap.client

import mail

Everything here is re-exported by mail, so import mail is enough and the names are called as mail.*. Importing mail.imap.client on its own works too and reaches the same definitions.

The reading end of IMAP.

ImapClient connects to a server, opens a mailbox, and reads what is in it. Unlike POP3, nothing is downloaded that was not asked for: a message’s headers, one part of it, or the whole of it, as needed.

import mail.imap { ImapClient }

var inbox = ImapClient.connect('imaps://mail.example.com', {
  username: 'ann',
  password: secret,
})

inbox.select('INBOX')

for id in inbox.search('UNSEEN') {
  echo inbox.fetch_message(id).subject()
}

inbox.logout()

Constants

IMAP_SCHEMES

mail.IMAP_SCHEMES = {...}

STATES

mail.STATES = [...]

Classes

Mailbox

class mail.Mailbox

An open mailbox, and what the server said about it when it opened.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.Mailbox(name: string)

Parameters

  • name (string)

Mailbox.to_string()

mail.Mailbox.to_string()

MailboxInfo

class mail.MailboxInfo

One mailbox as list() reports it: its name, the character that separates the levels of it, and what the server says about it.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.MailboxInfo(name: string, flags: list, delimiter: ?string)

Parameters

  • name (string)
  • flags (list) — The attributes, such as \HasChildren.
  • delimiter (?string) — The hierarchy separator, or nil for a server with no hierarchy at all.

MailboxInfo.is_selectable()

mail.MailboxInfo.is_selectable() -> bool

Whether the server says this name cannot be selected, which is what a folder that only holds other folders looks like.

Returns bool

MailboxInfo.to_string()

mail.MailboxInfo.to_string()

Envelope

class mail.Envelope

The addresses and dates out of a message’s headers, as the server parsed them, so a list of messages can be shown without fetching any of them.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.Envelope(values: list)

Envelope.sent_at()

mail.Envelope.sent_at() -> Date|nil

The date as a Date, or nil when the server sent none or it cannot be read.

Returns Date|nil

Envelope.to_string()

mail.Envelope.to_string()

BodyPart

class mail.BodyPart

What one message is made of, without fetching it: the type of each part, its size, and where in the message it sits.

section is the number a BODY[...] fetch uses to ask for that part on its own.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.BodyPart(type: string, subtype: string, parameters: dict, size: ?number, section: string)

Parameters

  • type (string)
  • subtype (string)
  • parameters (dict)
  • size (?number)
  • section (string)

BodyPart.mime_type()

mail.BodyPart.mime_type() -> string

The media type, as text/plain.

Returns string

BodyPart.walk()

mail.BodyPart.walk() -> list

Every part inside this one and itself, outermost first.

Returns list — of BodyPart

BodyPart.to_string()

mail.BodyPart.to_string()

MessageInfo

class mail.MessageInfo

What a FETCH returned about one message.

Which fields are filled in depends on what was asked for. parts holds each BODY[...] section that came back, keyed by the section that was requested.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.MessageInfo(sequence: number)

Parameters

  • sequence (number) — The message’s position in the mailbox.

MessageInfo.message()

mail.MessageInfo.message() -> Message|nil

The whole message, when the fetch asked for it.

Returns Message|nil

MessageInfo.is_seen()

mail.MessageInfo.is_seen() -> bool

Whether the message has been read.

Returns bool

MessageInfo.to_string()

mail.MessageInfo.to_string()

ImapClient

class mail.ImapClient

A connection to a server that stores mail.

  • printable — has a @to_string(), so echo and print() show something useful

Constructor

mail.ImapClient(connection, options: ?dict)

Builds a client around a connection that is already open.

Parameters

  • connection (LineStream)
  • options (?dict) — As connect().

ImapClient.connect()

mail.ImapClient.connect(url: string, options: ?dict) -> ImapClient

Opens a connection and gets as far as being able to select a mailbox.

optiondefaultwhat it does
username, passwordnoneauthenticate when both are given
tokennoneauthenticate with a bearer token instead
tlsrequirerequire, prefer or disable, when not already encrypted
tls_configa default onethe trust settings for TLS
mechanismsall of themrestricts which are acceptable
timeout60000milliseconds to wait on the socket

Parameters

  • url (string) — imaps://host, imap://host, or a bare host.
  • options (?dict)

Returns ImapClient

Raises ImapError if the server refuses.

Raises AuthenticationError if it refuses the credentials.

ImapClient.begin()

mail.ImapClient.begin() -> ImapClient

Reads the greeting, negotiates TLS and authenticates.

Returns ImapClient — itself.

Raises ImapError if the greeting says the server is not available.

ImapClient.supports()

mail.ImapClient.supports(name: string) -> bool

Whether the server offers a capability.

Parameters

  • name (string) — Matched without regard to case.

Returns bool

ImapClient.is_secure()

mail.ImapClient.is_secure() -> bool

Whether the connection is encrypted.

Returns bool

ImapClient.refresh_capabilities()

mail.ImapClient.refresh_capabilities() -> list

Asks the server what it can do, replacing what is known.

Returns list — of string

ImapClient.start_tls()

mail.ImapClient.start_tls() -> ImapClient

Negotiates TLS over a connection that started in the clear, and asks what the server can do again, because nothing it said before the handshake was protected.

Returns ImapClient — itself.

Raises ImapError if the server refuses.

ImapClient.login()

mail.ImapClient.login(mechanism: ?string) -> ImapClient

Authenticates, choosing the strongest mechanism both ends know.

A server may forbid a mechanism outright by advertising LOGINDISABLED, which is how it says the connection is not private enough; this refuses to try in that case rather than sending the password anyway.

Parameters

  • mechanism (?string) — Forces one rather than choosing.

Returns ImapClient — itself.

Raises AuthenticationError if there is no mechanism in common or the credentials are refused.

ImapClient.authenticate()

mail.ImapClient.authenticate(mechanism) -> ImapClient

Runs one authentication exchange with a mechanism already built.

Parameters

  • mechanism (Mechanism)

Returns ImapClient — itself.

Raises AuthenticationError if the server refuses.

ImapClient.run()

mail.ImapClient.run(name: string, arguments: list) -> list

Sends a command and reads everything it draws, up to and including the tagged answer.

Arguments are written as they are: a plain string goes unquoted, so quote anything that needs it with parser.quote() first. A Literal is sent as a literal, waiting for the server’s permission unless it said permission is not needed.

Parameters

  • name (string)
  • arguments (list)

Returns list — of the untagged responses the command produced.

Raises ImapError if the server answers NO or BAD.

ImapClient.on_event()

mail.ImapClient.on_event(listener) -> ImapClient

Calls a function with every untagged response the server sends.

This is how a program watches a mailbox: the server announces new mail and changed flags whenever it likes, and idle() exists so there is something to announce them during.

Parameters

  • listener (?function(1)) — nil stops listening.

Returns ImapClient — itself.

ImapClient.select()

mail.ImapClient.select(name: string) -> Mailbox

Opens a mailbox for reading and writing.

Parameters

  • name (string)

Returns Mailbox

Raises ImapError if there is no such mailbox.

ImapClient.examine()

mail.ImapClient.examine(name: string) -> Mailbox

Opens a mailbox without being able to change anything in it, which also means reading a message does not mark it read.

Parameters

  • name (string)

Returns Mailbox

Raises ImapError if there is no such mailbox.

ImapClient.close_mailbox()

mail.ImapClient.close_mailbox() -> ImapClient

Closes the open mailbox, removing the messages marked deleted on the way out.

Returns ImapClient — itself.

ImapClient.unselect()

mail.ImapClient.unselect() -> ImapClient

Closes the open mailbox without removing anything, which is what CLOSE cannot do.

Returns ImapClient — itself.

Raises StateError if the server does not offer UNSELECT.

ImapClient.list()

mail.ImapClient.list(reference: ?string, pattern: ?string) -> list

The mailboxes matching a pattern.

* matches anything including the hierarchy separator, and % matches anything except it, which is what lists one level.

for box in inbox.list('', '*') {
  echo box.name
}

Parameters

  • reference (?string) — The prefix to search under. Empty when not given.
  • pattern (?string) — * when not given.

Returns list — of MailboxInfo

ImapClient.lsub()

mail.ImapClient.lsub(reference: ?string, pattern: ?string) -> list

The mailboxes matching a pattern that this account is subscribed to.

Parameters

  • reference (?string)
  • pattern (?string)

Returns list — of MailboxInfo

ImapClient.status()

mail.ImapClient.status(name: string, items: ?list) -> dict

What is in a mailbox without opening it.

echo inbox.status('INBOX', ['MESSAGES', 'UNSEEN'])

Parameters

  • name (string)
  • items (?list) — MESSAGES, RECENT, UIDNEXT, UIDVALIDITY and UNSEEN. All five when not given.

Returns dict — of the items asked for, by name.

ImapClient.create()

mail.ImapClient.create(name: string) -> ImapClient

Creates a mailbox.

Parameters

  • name (string)

Returns ImapClient — itself.

Raises ImapError if it exists already or the name is not allowed.

ImapClient.delete()

mail.ImapClient.delete(name: string) -> ImapClient

Deletes a mailbox and everything in it.

Parameters

  • name (string)

Returns ImapClient — itself.

Raises ImapError if there is no such mailbox.

ImapClient.rename()

mail.ImapClient.rename(from: string, to: string) -> ImapClient

Renames a mailbox.

Parameters

  • from (string)
  • to (string)

Returns ImapClient — itself.

ImapClient.subscribe()

mail.ImapClient.subscribe(name: string) -> ImapClient

Marks a mailbox as one this account wants to see.

Parameters

  • name (string)

Returns ImapClient — itself.

ImapClient.unsubscribe()

mail.ImapClient.unsubscribe(name: string) -> ImapClient

Undoes subscribe().

Parameters

  • name (string)

Returns ImapClient — itself.

ImapClient.search()

mail.ImapClient.search(criteria: string, by_uid: ?bool) -> list

The messages in the open mailbox matching a search.

The criteria are IMAP’s own, which read close to English: 'UNSEEN', 'FROM ann@example.com', 'SINCE 1-Jan-2026 SMALLER 10000'.

for uid in inbox.search('UNSEEN SINCE 1-Jan-2026', true) {
  echo uid
}

Parameters

  • criteria (string)
  • by_uid (?bool) — Return unique identifiers rather than positions, which is what anything that outlives the connection wants.

Returns list — of number

Raises StateError if no mailbox is open.

ImapClient.fetch()

mail.ImapClient.fetch(ids, items: ?string, by_uid: ?bool) -> list

Fetches what the server knows about some messages.

for info in inbox.fetch('1:10', 'ENVELOPE FLAGS', false) {
  echo '${info.sequence} ${info.envelope.subject}'
}

Parameters

  • ids (string|list|number) — A set, as '1:10' or '1,3,5', or a list of numbers, or one number.
  • items (?string) — What to fetch, in IMAP’s own words. ENVELOPE FLAGS INTERNALDATE RFC822.SIZE when not given.
  • by_uid (?bool) — Whether ids are unique identifiers.

Returns list — of MessageInfo

Raises StateError if no mailbox is open.

ImapClient.fetch_message()

mail.ImapClient.fetch_message(id: number, by_uid: ?bool, mark_seen: ?bool) -> Message

Fetches one message whole.

Reading a message this way does not mark it read, because a program that goes through a mailbox should not change what a person sees when they next open it. Pass mark_seen to say otherwise, which is what a mail client showing a message wants.

echo inbox.fetch_message(uid, true, false).subject()

Parameters

  • id (number)
  • by_uid (?bool)
  • mark_seen (?bool) — Whether to set \Seen on the way.

Returns Message

Raises ImapError if there is no such message.

ImapClient.fetch_headers()

mail.ImapClient.fetch_headers(ids, by_uid: ?bool) -> list

Fetches only the headers of some messages, which is enough to show a list without pulling the bodies across.

Parameters

  • ids (string|list|number)
  • by_uid (?bool)

Returns list — of MessageInfo, each with parts holding the header block.

ImapClient.store()

mail.ImapClient.store(ids, action: string, flags: list, by_uid: ?bool) -> list

Changes the flags on some messages.

Parameters

  • ids (string|list|number)
  • action (string) — FLAGS to replace, +FLAGS to add, -FLAGS to remove. Add .SILENT to any of them to stop the server reporting the result back.
  • flags (list) — The flags, as ['\\Seen'].
  • by_uid (?bool)

Returns list — of MessageInfo describing what changed.

ImapClient.add_flags()

mail.ImapClient.add_flags(ids, flags: list, by_uid: ?bool) -> list

Adds flags to some messages, leaving the rest alone.

Parameters

  • ids (string|list|number)
  • flags (list)
  • by_uid (?bool)

Returns list — of MessageInfo

ImapClient.remove_flags()

mail.ImapClient.remove_flags(ids, flags: list, by_uid: ?bool) -> list

Removes flags from some messages.

Parameters

  • ids (string|list|number)
  • flags (list)
  • by_uid (?bool)

Returns list — of MessageInfo

ImapClient.mark_seen()

mail.ImapClient.mark_seen(ids, by_uid: ?bool) -> list

Marks messages read.

Parameters

  • ids (string|list|number)
  • by_uid (?bool)

Returns list — of MessageInfo

ImapClient.mark_deleted()

mail.ImapClient.mark_deleted(ids, by_uid: ?bool) -> list

Marks messages for removal. Nothing goes until expunge().

Parameters

  • ids (string|list|number)
  • by_uid (?bool)

Returns list — of MessageInfo

ImapClient.copy()

mail.ImapClient.copy(ids, target: string, by_uid: ?bool) -> ImapClient

Copies messages into another mailbox.

Parameters

  • ids (string|list|number)
  • target (string)
  • by_uid (?bool)

Returns ImapClient — itself.

ImapClient.move()

mail.ImapClient.move(ids, target: string, by_uid: ?bool) -> ImapClient

Moves messages into another mailbox.

Uses the server’s own MOVE where there is one, and otherwise does what MOVE was invented to replace: copy, mark deleted, expunge.

Parameters

  • ids (string|list|number)
  • target (string)
  • by_uid (?bool)

Returns ImapClient — itself.

ImapClient.expunge()

mail.ImapClient.expunge() -> list

Removes every message marked deleted in the open mailbox.

Returns list — of number: the positions that went, highest first, which is the order they have to be applied in.

ImapClient.append()

mail.ImapClient.append(mailbox: string, note, flags: ?list, received) -> ImapClient

Adds a message to a mailbox without sending it anywhere, which is how a sent message gets into the Sent folder.

Parameters

  • mailbox (string)
  • note (Message|string|bytes)
  • flags (?list) — ['\\Seen'] is the usual one for a sent message.
  • received (?Date) — The internal date to file it under. The time of arrival when not given.

Returns ImapClient — itself.

ImapClient.idle()

mail.ImapClient.idle(timeout: ?number) -> list

Waits for the server to say something, which is how a program learns about new mail without asking over and over.

Returns when the server reports anything, or when the wait runs out. A server may drop a connection that idles for too long, so even a program with nothing else to do should come back around every twenty minutes or so; RFC 2177 says as much.

inbox.on_event(@(response) {
  echo 'server said ${response.name()}'
})

while true {
  inbox.idle(1500000)
}

Parameters

  • timeout (?number) — Milliseconds to wait. 1500000, which is twenty-five minutes, when not given.

Returns list — of the untagged responses that arrived.

Raises StateError if the server does not offer IDLE.

ImapClient.noop()

mail.ImapClient.noop() -> list

Asks the server for nothing, which both keeps the connection alive and gives it a chance to report anything that has changed.

Returns list — of the untagged responses that came with it.

ImapClient.namespace()

mail.ImapClient.namespace()

The namespaces this account has: its own, other people’s, and the shared ones.

Returns — list, as the server sent it.

Raises StateError if the server does not offer NAMESPACE.

ImapClient.enable()

mail.ImapClient.enable(names: list) -> ImapClient

Turns on an extension the server offers but does not use until asked, such as UTF8=ACCEPT.

Parameters

  • names (list)

Returns ImapClient — itself.

Raises StateError if the server does not offer ENABLE.

ImapClient.id()

mail.ImapClient.id(fields: ?dict) -> dict

Tells the server what this client is, and reads back what the server is. Both sides may say nothing at all.

Parameters

  • fields (?dict) — name, version and whatever else is worth saying. A name of Zuri when not given.

Returns dict — of what the server said about itself.

ImapClient.logout()

mail.ImapClient.logout() -> ImapClient

Says goodbye and closes the connection.

Returns ImapClient — itself.

ImapClient.close()

mail.ImapClient.close() -> ImapClient

Closes the connection without saying goodbye.

Returns ImapClient — itself.

ImapClient.to_string()

mail.ImapClient.to_string()

2026, Richard Ore and Zuri contributors