-
Notifications
You must be signed in to change notification settings - Fork 17
Issue 45 Verify Button Only Worked For Microsoft
Click Verify beside the mail folder on a Basic IMAP mailbox and it answered:
Mailbox is not authenticated
The mailbox was authenticated. It was listed as Connected, it was being checked on schedule, and it was collecting mail normally. Only the button disagreed.
Reported by mbsouth on issue #45 β the issue that added Basic IMAP support in the first place, which is exactly what this bug is: somewhere that feature never reached. He had already worked out the important half himself, reporting that mail to and from his Postfix/Dovecot servers was "working fine" and that it was only the warnings he could not identify. He was right on both counts.
Fixed in a1f64426, released as update #1171.
The companion bug reported alongside this one is IMAP mailboxes reported as not authenticated. The general guides are Mailbox authentication and Basic IMAP mailboxes.
verify_mailbox_folder.php was built for Microsoft Graph, back when Graph was the only thing a mailbox could be. Basic IMAP and Google arrived later. The button was shown for all three β the mailbox dialogue hides fields by provider, but the Email folder row carries none of those classes, so every mailbox gets the button β and nothing in the endpoint had ever been taught that the other two exist.
| Provider | What Verify did |
|---|---|
| Microsoft | Worked. The only one it was written for |
| Basic IMAP | Refused at the door, every time, on every install |
| Ran the entire Microsoft flow using Google credentials |
The first thing the endpoint did was look for an OAuth token:
if (empty($mailbox['token_data'])) {
echo json_encode(['success' => false, 'error' => 'Mailbox is not authenticated']);
exit;
}token_data holds the OAuth token. A Basic IMAP mailbox does not have one and does not need one β it connects with a stored username and password. No IMAP code path ever writes that column; the only mention of it in save_mailbox.php sets it to NULL.
So the test was not usually false for IMAP, it was structurally false. No credential could ever satisfy it. The failure was deterministic on every install, for every IMAP mailbox, and re-entering the password could not possibly help, because the password was not what was being examined.
The one mercy is that it stopped there. No connection was attempted to anybody's mail server, and nothing was sent to Microsoft.
A Google mailbox does hold a token β a Google one β so it sailed through the gate and into a function that assumed any token was Microsoft's:
- the access token was sent to
https://graph.microsoft.com/v1.0/..., which rejected it - an expired token was refreshed against
https://login.microsoftonline.com/β¦/oauth2/v2.0/token, usingazure_client_idandazure_client_secretthat a Google mailbox has never had
The user was shown a Microsoft error about a mailbox with nothing to do with Microsoft. Only the identity-mismatch check was properly fenced behind provider === 'microsoft'; everything after it was Microsoft by assumption rather than by decision.
The general lesson, and it is #77's lesson again one turn further on: that write-up ended by saying the same question asked in two places will eventually be answered two different ways. This is what happens when a provider is added and a Microsoft-shaped function is left in the path β the question was never re-asked at all, so two of the three answers were Microsoft's.
Each provider is verified its own way, and the branch happens before the token gate β which is precisely where IMAP used to die.
Through the same helper the reader uses. IMAP verification opens the folder with imapStreamFor(), the function the inbound poll itself calls. Verify cannot now confirm a folder that reading then rejects, which was the exact failure shape of #77.
A folder that is not found names the folders that are. On a server with an INBOX. personal namespace the real name is INBOX.Support, and Support on its own genuinely will not open β so "not found" without a list is a puzzle rather than an answer:
Folder "NoSuchFolder" was not found in this mailbox.
Folders on the server: INBOX, Drafts, Templates, Snoozed, Sent, Spam, Trash, Archive.
Folder names arrive in IMAP's own modified UTF-7, so they are decoded before being shown β otherwise a folder called Anfragen or Γbergabe comes back mangled in the one message whose whole purpose is to be read.
Connection failures are told apart from missing folders. The connection is made against INBOX, which always exists, so a failure at that point is unambiguously credentials, TLS or the missing PHP extension β not a mistyped folder name.
Gmail is checked against labels, because Gmail has labels. The label is looked up by name, case-insensitively, and a miss lists the labels that exist.
Gmail counts mirror the reader, not the label. gmailGetEmails() always lists with labelIds=INBOX and applies the configured folder as an extra label: filter on top β so it only ever sees mail that is in the Inbox and carries the label. Reporting the label's own message count would have passed a label whose mail is all archived, which reading then collects nothing from. Instead Verify runs the reader's own query, and says so plainly when the label exists but its mail sits outside the Inbox:
The label holds 42 message(s), but none are in the Inbox. FreeITSM only reads mail that is in the Inbox AND labelled "Support", so archived mail carrying this label will not be collected.
That result is shown in amber rather than green, because "found" in green reads as "working".
| File | Role |
|---|---|
includes/mailbox_imap.php |
New: imapVerifyFolder() β connects via imapStreamFor(), matches the folder against the server's own list, returns real message counts. New: imapListFolders() β strips the {host} prefix and decodes modified UTF-7 |
includes/gmail.php |
New: gmailVerifyFolder() β resolves the label, then counts through the reader's own query rather than the label's totals |
| File | Role |
|---|---|
api/tickets/verify_mailbox_folder.php |
Branches on provider before the OAuth-token gate; Google refreshes through gmailGetValidAccessToken(), not Microsoft's endpoint. The Microsoft path is untouched |
tickets/settings/index.php |
Renders the Gmail caveat in amber; stopped double-encoding a folder name containing & or a quote (escapeHtml into textContent) |
Run live against a real installation carrying all three provider types at once β a Microsoft 365 mailbox, a Gmail mailbox and a Zoho Basic IMAP mailbox β so each result has the other two beside it as controls:
| Check | Result |
|---|---|
IMAP, INBOX
|
Found, with counts |
IMAP, a real subfolder (Archive) |
Found |
| IMAP, a folder that does not exist | Named it, and listed the ten real folders on the server β which is itself proof the login succeeded and a genuine IMAP LIST ran |
Google, INBOX
|
Found, with counts from the live account |
| Google, a label that does not exist | Named it, and listed the account's real labels |
Microsoft, INBOX |
Unchanged β still resolves to a folder id and reads back |
| Microsoft, unknown folder | Unchanged β still its own "check the spelling, use a slash" message |
The two Microsoft rows are the regression control, and they matter as much as the new ones: the fix had to leave the only provider that already worked exactly as it was.
What is not proven. The mailbox tested is a hosted Zoho account. The reporter's is a self-hosted Postfix/Dovecot pair, which was not available to test against. The defect itself was structural rather than server-specific β the OAuth-token check could not pass for any IMAP server, whoever ran it β but Dovecot commonly presents an INBOX. personal namespace, and that shape was not exercised live. It is precisely the case the folder list in the error message exists to explain.
The IMAP mailbox tested has a flat folder layout, so the delimiter-normalising match (accepting Inbox/Support where the server names it INBOX.Support) was exercised only against flat names. The Gmail out-of-Inbox caveat was reasoned from the reader's query and not reproduced against an account with an archived label. And the PHP IMAP extension was present on the test machine, so the "extension not enabled" message was not seen live β it is raised by imapStreamFor(), which the mail collection has always used.
- Verify works on all three provider types now. On IMAP it really opens the folder; on Gmail it really looks the label up.
- If Verify told you a working IMAP mailbox was not authenticated, nothing was wrong with your mailbox and nothing needed reconnecting. Mail was being collected the whole time.
-
If a folder is not found, read the list in the message. On many servers the name you want is
INBOX.Something, notSomething. - On Gmail, watch for the amber result. A label whose mail has been archived out of the Inbox will not be collected, and that is worth knowing before you wonder why no tickets appear.
- Bugs resolved β the index of write-ups like this one
- IMAP mailboxes reported as not authenticated β the other half of the same report
- Mail could only ever be collected from Inbox β the folder-resolution bug this one rhymes with
- Verifying a mail folder β Developer Guide β how Graph, IMAP and Gmail are each checked, with the code
- Basic IMAP mailboxes Β· Mailbox authentication
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)