-
-
Notifications
You must be signed in to change notification settings - Fork 29
Replies With Thread Pictures Failed To Send
Reported in #158 Β· Broken in 2.10.0 Β· Fixed in #2081 Β· Ships in 2.10.1
This is a fault in the 2.10.0 fix for Reply attachments never reached the customer. π οΈ The code underneath: Outbound email: attachments and pictures β Developer Guide
Thanks to An Duong (@duongtuanan), who upgraded to 2.10.0, found the error in the log, traced it to the exact line, and tested a fix before reporting it.
After upgrading to 2.10.0, some replies and forwards would not send and others went out normally. The analyst got an error, nothing reached the customer, and the server's PHP log said:
PHP Fatal error: Uncaught Error: Undefined constant "INLINE_THREAD_BUDGET"
in /var/www/html/api/tickets/send_email.php:437
It happened on every mail provider β Microsoft, Gmail and SMTP alike.
The deciding factor was the quoted thread underneath the reply:
| The reply or forward⦠| 2.10.0 |
|---|---|
| quotes an earlier email on the ticket that had a picture in it (a logo in a signature counts) | β failed |
| quotes a thread with no pictures | β sent |
| is a brand-new email (no thread) | β sent |
| has a pasted screenshot or an attached file, but no picture in the thread | β sent |
So it depended on the ticket, not the analyst or the mailbox β which is why it looked random. Customers whose email signatures carry a logo would have hit it on almost every ticket.
2.10.0 started embedding the thread's pictures inside the email, with a limit of 2 MB per email. That limit was written as a constant, placed next to the function that uses it β near the bottom of send_email.php:
// ...the code that sends, around line 150, calls processInlineImages()...
const INLINE_THREAD_BUDGET = 2 * 1024 * 1024; // around line 389
function processInlineImages($body, $ticketId) {
// ...
if ($threadBytes + $size > INLINE_THREAD_BUDGET) {PHP treats these two differently. A function anywhere in a file exists from the moment the file starts running. A top-level const only exists once PHP has actually run that line. This file does its work at the top and keeps its functions at the bottom, so by the time a reply was being sent, PHP had never reached line 389 β the constant did not exist yet.
The constant was only read when a picture from the ticket was found in the thread. No picture, no read, no error. That is the whole of the "some emails go out, some don't".
tests/outbound-email-mime.php cannot run send_email.php as a whole β it is a web endpoint that sends the moment it is loaded β so it loaded just the bottom half, the functions. That bottom half begins above the constant, so in the test the constant was always defined before anything used it. The test passed every picture check, because it was not running the file the way the server does.
The live sends made while building the fix all went out β because none of them quoted a picture already stored on the ticket. Any one that had would have failed.
π A test that loads only part of a file can pass on code that cannot run. The order things happen in is part of the code.
-
The constant moved to the top of the file, under the
requirelines and above anything that sends, with a comment saying why it must stay there. -
A fault embedding one picture can no longer stop the email. The picture code caught
Exception, but a missing constant is anError, which slipped past it. It now catches both (Throwable), and the picture keeps its link β the same thing that happens past the 2 MB limit. -
The test checks the order. A new section reads the endpoint's source and fails if any constant is declared below the code that sends. Run against 2.10.0, it fails and names
INLINE_THREAD_BUDGET.
A sweep of every other endpoint found no other file with a constant below its working code.
| File | Change |
|---|---|
api/tickets/send_email.php |
INLINE_THREAD_BUDGET declared at the top; the picture callback catches Throwable
|
tests/outbound-email-mime.php |
New section 0: every constant is declared above the code that sends; the constants are now loaded for the other checks the same way |
The bug was reproduced first, through the real endpoint (api/tickets/send_email.php, posted to exactly as the reply box does). Nine throwaway tickets were made β three per provider β and on 2.10.0 every reply or forward whose thread held a picture failed with the reporter's exact error, on Microsoft, Gmail and SMTP. Nothing was sent.
With the fix, nine numbered emails were sent and received:
| # | Provider | Scenario |
|---|---|---|
| 1 | Microsoft | Reply, thread has a picture β the case that failed |
| 2 | Microsoft | Reply, no pictures β the case that always worked |
| 3 | Microsoft | Forward with a thread picture, a pasted screenshot and an attached file |
| 4β6 | Gmail | The same three |
| 7β9 | SMTP | The same three |
php tests/outbound-email-mime.php β 29 checks pass; against 2.10.0's send_email.php the new section fails, as it should.
-
On 2.10.0? Upgrade to 2.10.1. Until you can, the reporter's own fix works: move the
const INLINE_THREAD_BUDGET = 2 * 1024 * 1024;line inapi/tickets/send_email.phpup to just below therequire_oncelines. - Replies that failed were not sent, and were not saved on the ticket. The analyst was told it failed, so there is nothing hidden to find β but if one was not retried, the customer is still waiting for it.
- Nothing to configure. Earlier versions are not affected; this was new in 2.10.0.
- Reply attachments never reached the customer β the 2.10.0 fix this corrects
- Outbound email: attachments and pictures β Developer Guide
- Bugs resolved
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ 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: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° 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
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- 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: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- 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
- β³ π’ One board across every company
- β³ 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)
- What this is
-
π Bugs resolved
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96