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()
}
| Option | Type | Description |
|---|---|---|
host | string | Hostname of the IMAP server. |
port | number | Port of the IMAP server (usually 993 with TLS or 143 without). |
tls | boolean | Whether 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.username | string | Username used for authentication. |
auth.password | string | Password used for authentication (AUTHENTICATE PLAIN / LOGIN). |
auth.accessToken | string | OAuth 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. |
timeoutMs | number | Read 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:
- When an OAuth 2.0 access token is configured (
auth.accessTokenorauth.getAccessToken) and the server advertisesAUTH=XOAUTH2, theXOAUTH2SASL mechanism is used — required by Gmail, Microsoft 365 / Outlook.com, Yahoo and other providers that no longer accept passwords. AUTHENTICATE PLAINwith a SASL initial response is tried when the server advertisesAUTH=PLAIN.LOGINis used as a fallback — but never when the server advertisesLOGINDISABLED(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
}
}
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.