Skip to main content

Initialisation

The CFImap class can be created in any part of the code, however it is advised to use the connect() function only in a request handler. That is because the Cloudflare Workers platform limits some functionality (mainly await) outside of handlers.

Usage

The constructor takes an Options object:

import { CFImap } from "cf-imap"

const imap = new CFImap({
host: "mail.example.com",
port: 993,
tls: true,
auth: {
username: "[email protected]",
password: "pa$$w0rd"
},
timeoutMs: 30000 // optional, defaults to 30000
})

const handleRequest = async () => {
await imap.connect()
}
OptionTypeDescription
hoststringHostname of the IMAP server.
portnumberPort of the IMAP server (usually 993 with TLS or 143 without).
tlsbooleanWhether to use TLS. On port 993 (the conventional Implicit TLS port, RFC 8314) TLS is negotiated immediately; on any other port the STARTTLS command is used (RFC 9051 §6.2.1).
auth.usernamestringUsername used for authentication.
auth.passwordstringPassword used for authentication (AUTHENTICATE PLAIN / LOGIN).
auth.accessTokenstringOAuth 2.0 access token used for authentication (AUTHENTICATE XOAUTH2). Either this or auth.getAccessToken instead of auth.password.
auth.getAccessToken() => string \| Promise<string>Called at connect() time to obtain a fresh access token (AUTHENTICATE XOAUTH2). Either this or auth.accessToken instead of auth.password.
timeoutMsnumberRead timeout for IMAP responses in milliseconds (optional, defaults to 30000).

connect()

Connects to the IMAP server, reads the greeting, negotiates TLS if configured, authenticates and enables IMAP4rev2 when the server supports it. On success the session is stored on the instance:

await imap.connect()

imap.session // { id?: string, protocol?: string }
imap.capabilities // string[], e.g. ["IMAP4rev1", "UIDPLUS", "MOVE"]

Authentication

Authentication is handled automatically per RFC 9051 §6.2.2/§6.2.3:

  1. When an OAuth 2.0 access token is configured (auth.accessToken or auth.getAccessToken) and the server advertises AUTH=XOAUTH2, the XOAUTH2 SASL mechanism is used — required by Gmail, Microsoft 365 / Outlook.com, Yahoo and other providers that no longer accept passwords.
  2. AUTHENTICATE PLAIN with a SASL initial response is tried when the server advertises AUTH=PLAIN.
  3. LOGIN is used as a fallback — but never when the server advertises LOGINDISABLED (in which case a descriptive error is thrown, with a hint when the server requires OAuth2).

OAuth 2.0 authentication

Providers like Gmail (imap.gmail.com:993) and Microsoft 365 / Outlook.com (outlook.office365.com:993) require OAuth2 — passwords are rejected. Pass a short-lived access token instead of a password:

const imap = new CFImap({
host: "imap.gmail.com",
port: 993,
tls: true,
auth: {
username: "[email protected]",
accessToken: "ya29.a0AfH6SM..." // an OAuth 2.0 access token
}
})

Because access tokens expire (typically after ~1 hour), use getAccessToken to hand the library a fresh token on every connect() — the callback is invoked right before the AUTHENTICATE XOAUTH2 exchange:

auth: {
username: "[email protected]",
getAccessToken: async () => {
// e.g. exchange a stored refresh token for a new access token
// (stored in a Workers secret / KV) via the provider's token endpoint
const res = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
body: new URLSearchParams({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
refresh_token: REFRESH_TOKEN,
grant_type: "refresh_token"
})
})
const data = await res.json()
return data.access_token
}
}
note

The library only performs the IMAP AUTHENTICATE XOAUTH2 exchange — it does not run the OAuth 2.0 flow itself. Obtaining the initial token (authorization code + PKCE or a service-account JWT for Workspace domain-wide delegation) and refreshing it is up to your Worker code. Scopes: Gmail requires https://mail.google.com/; Microsoft requires https://outlook.office.com/IMAP.AccessAsUser.All (with offline_access for refresh tokens).

On failure the server replies with a + <base64 JSON> error challenge (status/schemes/scope). cf-imap decodes it, acknowledges the challenge as the protocol requires, and throws an ImapError describing the problem — e.g. an expired token ("status 401 — the access token is expired or invalid").

IMAP4rev2 negotiation

When a server advertises both IMAP4rev1 and IMAP4rev2, the client must issue ENABLE IMAP4rev2 to get IMAP4rev2 behavior (RFC 9051 Appendix A) — connect() does this automatically.

If the server rejects the login (or any response is an error), a descriptive ImapError is thrown.

From now on, the docs will assume that the initialised CFImap class is called imap.