Skip to main content

connectError

The connectError webhook event is triggered when EmailEngine cannot reach an account's mail server, or can reach it but the server cannot serve the account right now. It covers network-level and server-level failures, as distinct from a credential the server refused, which is authenticationError.

When This Event is Triggered​

For an IMAP account, the connectError event fires when a connection attempt fails for a reason other than refused credentials:

  • The server is unreachable (network timeout, DNS failure)
  • The server refuses the connection (port closed, firewall blocking)
  • The TLS handshake fails
  • The server returns an error before authentication, or drops the connection during it
  • The server answers the login with a NO that is its own problem rather than the credential's: one carrying the RFC 5530 code UNAVAILABLE, SERVERBUG, INUSE or LIMIT, or Exchange Online's User is authenticated but not connected., which its front end sends when the mailbox's backend cannot be reached and also, permanently, when IMAP is disabled for the mailbox. Before EmailEngine 2.80.1 these were reported as authenticationError
  • The credential service the account depends on could not be reached, or answered 408, 429 or a 5xx status: the OAuth2 token endpoint for an account that authenticates with OAuth2, or the external authentication server. Before 2.80.0 these were reported as authenticationError

For a Microsoft Graph account, since 2.80.0, it fires when the account's change subscription cannot be created or renewed after the retries are spent, with serverResponseCode: "SubscriptionSetupError". A Graph account has no polling fallback, so without a subscription it sees no new mail at all; a tenant that disables the Exchange Online service principal hits this while every token refresh keeps succeeding. The account stays in the connectError state, and its own API operations answer 503, until a subscription can be established again; EmailEngine keeps trying once an hour.

Gmail API accounts do not send connectError. A transport failure against the Gmail API is retried, and a rejected token is authenticationError.

EmailEngine reports the first occurrence of a failure and suppresses repeats until the error changes or the account connects. See Webhook Deduplication.

A connection attempt that EmailEngine itself interrupted, because the account was paused, deleted or reconfigured while it was connecting, does not fire the event.

Payload Schema​

Top-Level Fields​

FieldTypeRequiredDescription
serviceUrlstring or nullYesThe configured EmailEngine service URL. null when the serviceUrl setting is empty
accountstringYesAccount ID that experienced the connection failure
datestringYesISO 8601 timestamp when the webhook was generated
eventstringYesAlways connectError
dataobjectYesError details (see below)

Error Data Fields (data object)​

FieldTypeRequiredDescription
responsestringYesThe server's response text if it sent one, otherwise the error message from the connection attempt
serverResponseCodestringNoThe server's response code if it sent one, otherwise the error code from the connection attempt, such as Node.js's ECONNREFUSED, or one of EmailEngine's own codes below. Missing when the failure carried neither

There is no event ID in the body. EmailEngine sends it in the X-EE-Wh-Event-Id request header. See Delivery and Retries.

Server Response Codes​

Most connection failures carry a Node.js system error code as serverResponseCode:

CodeDescription
ECONNREFUSEDConnection refused: nothing is accepting connections on the host and port
ECONNRESETConnection reset: the server closed the connection unexpectedly
ETIMEDOUTConnection timed out: no response from the server
ENOTFOUNDDNS lookup failed: the hostname could not be resolved
EHOSTUNREACHHost unreachable: no route to the server
ECONNABORTEDConnection aborted
CERT_HAS_EXPIREDThe server's TLS certificate has expired
UNABLE_TO_VERIFY_LEAF_SIGNATUREThe server's TLS certificate could not be verified
SELF_SIGNED_CERT_IN_CHAINA self-signed certificate is in the chain
DEPTH_ZERO_SELF_SIGNED_CERTThe server presented a self-signed certificate

When the server answered but could not serve the account, the code is the IMAP response code from that answer:

CodeDescription
UNAVAILABLEA subsystem the login depends on is down
SERVERBUGThe server reported an internal error
INUSEThe mailbox is in use by another session, on a server that allows only one
LIMITA server limit was hit

Exchange Online's User is authenticated but not connected. carries no response code, so that payload has response and no serverResponseCode.

Codes EmailEngine sets itself:

CodeAccount typeDescription
ETokenRefreshIMAP with OAuth2The OAuth2 token endpoint answered 408, 429 or a 5xx status, so the access token could not be renewed. A refusal is authenticationError instead. A token endpoint that could not be reached at all reports the transport error, such as fetch failed, with no code
HTTPRequestErrorIMAP, with an authentication serverThe authentication server answered 408, 429 or a 5xx status. A refusal is authenticationError instead. A server that could not be reached at all reports the transport error, with no code
SubscriptionSetupErrorMicrosoft GraphThe change subscription could not be created or renewed. response carries the last error Microsoft Graph returned, or MS Graph subscription creation failed / MS Graph subscription renewal failed when none was recorded

Example Payload (Connection Refused)​

{
"serviceUrl": "https://emailengine.example.com",
"account": "user123",
"date": "2025-10-17T14:30:00.000Z",
"event": "connectError",
"data": {
"response": "connect ECONNREFUSED 192.168.1.100:993",
"serverResponseCode": "ECONNREFUSED"
}
}

Example Payload (DNS Failure)​

{
"serviceUrl": "https://emailengine.example.com",
"account": "remote-user",
"date": "2025-10-17T16:20:00.000Z",
"event": "connectError",
"data": {
"response": "getaddrinfo ENOTFOUND mail.invalid-domain.example",
"serverResponseCode": "ENOTFOUND"
}
}

Example Payload (TLS Certificate Error)​

{
"serviceUrl": "https://emailengine.example.com",
"account": "secure-account",
"date": "2025-10-17T17:00:00.000Z",
"event": "connectError",
"data": {
"response": "certificate has expired",
"serverResponseCode": "CERT_HAS_EXPIRED"
}
}

Example Payload (Server Could Not Serve the Login)​

An Exchange Online mailbox whose backend was unreachable at login:

{
"serviceUrl": "https://emailengine.example.com",
"account": "office-account",
"date": "2025-10-17T15:45:00.000Z",
"event": "connectError",
"data": {
"response": "User is authenticated but not connected."
}
}

Example Payload (Microsoft Graph Subscription)​

{
"serviceUrl": "https://emailengine.example.com",
"account": "outlook-user789",
"date": "2025-10-17T18:10:00.000Z",
"event": "connectError",
"data": {
"response": "MS Graph subscription creation failed",
"serverResponseCode": "SubscriptionSetupError"
}
}

Handling the Event​

Most codes describe the network between EmailEngine and the mail server, which EmailEngine keeps retrying on its own. SubscriptionSetupError is the one that needs a tenant administrator rather than time:

async function handleConnectError(event) {
const { account, data } = event;

switch (data.serverResponseCode) {
case 'SubscriptionSetupError':
// A Microsoft 365 tenant problem. Re-authorizing the account does not help
await notifyAdmin(account, {
message: 'Microsoft Graph change subscription failed, check the tenant',
error: data.response
});
break;
case 'ENOTFOUND':
await notifyAdmin(account, {
message: 'DNS lookup failed, verify the mail server hostname',
error: data.response
});
break;
case 'CERT_HAS_EXPIRED':
case 'UNABLE_TO_VERIFY_LEAF_SIGNATURE':
case 'SELF_SIGNED_CERT_IN_CHAIN':
case 'DEPTH_ZERO_SELF_SIGNED_CERT':
await notifyAdmin(account, {
message: 'TLS certificate error on the mail server',
error: data.response
});
break;
default:
// Unreachable or overloaded. EmailEngine retries; record it and watch for recovery
await db.accounts.update({
where: { emailEngineId: account },
data: { status: 'connection_error', lastError: data.response }
});
}
}

Distinguishing connectError from authenticationError​

AspectconnectErrorauthenticationError
When triggeredThe connection cannot be established, or the server or credential service cannot serve the account right nowThe server, provider or authentication server refused the credential
Typical causesNetwork issues, server down, firewall, TLS problems, a throttled token endpoint, a lost Graph subscriptionInvalid credentials, expired or revoked OAuth2 grants
Account stateconnectErrorauthenticationError, then unset once the account is switched off
User actionUsually none, wait for the server to recover. A tenant administrator for SubscriptionSetupErrorUpdate credentials or re-authorize
ResolutionAutomatic once the server is reachableRequires new credentials
Recovery webhookNone, the account state returns to connectedauthenticationSuccess

Webhook Deduplication​

EmailEngine stores the last error for each account and compares each new failure against it:

  1. First occurrence - Reported at once, and the stored error is set
  2. Same code - A failure with the same serverResponseCode as the stored error is treated as a repeat and not reported, even if response differs
  3. Different code - A different serverResponseCode is a new failure and is reported. A server that goes from ETIMEDOUT to ECONNREFUSED while it is being restarted therefore produces two webhooks
  4. No code - When neither error carries a code, the whole data object is compared, and any difference is reported
  5. Recovery - A successful login clears the stored error, so the next failure is a first occurrence again. No webhook is sent for it: since EmailEngine 2.80.0 authenticationSuccess reports recovery from an authenticationError only. A SubscriptionSetupError is not cleared by a login at all, only by a subscription that works again, because a Graph account can log in perfectly while it is unable to subscribe

The stored error is shared between connectError and authenticationError, so a connection failure that follows an unresolved authentication failure is reported as a change, and the reverse likewise.

Automatic Retry Behavior​

After a connection failure an IMAP account stays in the connectError state and EmailEngine keeps retrying on its own, with exponential backoff that starts at 2 seconds and is capped at 10 minutes between attempts. The retries continue until:

  • The connection succeeds
  • The account is deleted, paused, or disabled
  • The account configuration is updated, which starts a fresh connection

A Microsoft Graph account whose subscription failed is retried by the hourly subscription pass.

You do not need to request a reconnect from your webhook handler; one only shortens the wait until the next attempt.

See Also​