Note: I'm not a deep PHP/regex expert and don't fully understand the root cause — I investigated this together with Claude Code and am filing what we found. Happy to provide more data or test a patch.
Summary
Sending a reply to a customer fails with a Send error ("Fehler senden") whenever the conversation contains an email whose stored HTML body is very large and consists almost entirely of blank/whitespace lines (a bloated Outlook/Exchange signature). The plain-text email view (emails/customer/reply_fancy_text.blade.php) runs the body through Helper::htmlToText(), the underlying Html2Text conversion's preg_replace() returns null, and that null is then passed straight into preg_replace_callback(), which on PHP 8.1+ raises a deprecation that gets escalated to a hard error and aborts the send.
Error message
Fehler senden. preg_replace_callback(): Passing null to parameter #3 ($subject) of type array|string is deprecated (View: /…/freescout/resources/views/emails/customer/reply_fancy_text.blade.php). Message-ID: FS_reply-XXXXXX-XXXXXXXXXXXXXXXX@example.com
Environment
- FreeScout: current release (reproduced against the bundled
html2text/html2text library)
- PHP: 8.1+ (the deprecation only surfaces on 8.1+; on some hosts it is escalated to a fatal/error)
- Hosting: shared hosting with default/tighter PCRE limits (
pcre.backtrack_limit, pcre.recursion_limit, pcre.jit)
Where it happens
resources/views/emails/customer/reply_fancy_text.blade.php renders the body:
{!! \Helper::htmlToText($thread->body, true) !!}
Helper::htmlToText() (app/Misc/Helper.php) hands the body to Html2Text, and inside the library (vendor/html2text/html2text/src/Html2Text.php, converter()):
$text = preg_replace($this->search, $this->replace, $text); // ← can return NULL on a pathological body
$text = preg_replace_callback($this->callbackSearch, [...], $text); // ← NULL passed here → the reported error
preg_replace() returns null when the PCRE engine hits a limit (e.g. PREG_BACKTRACK_LIMIT_ERROR, PREG_RECURSION_LIMIT_ERROR, or a JIT stack limit). The library does not check for null before passing the value to preg_replace_callback() on the next line.
Root cause of the pathological body
The triggering email is a corporate Outlook/Exchange message with a heavy HTML signature (logo + social icons + a large image). FreeScout stored the body verbatim, including a huge run of empty whitespace lines inside the signature <table>:
- Stored body size: ~2.79 MB
- Total lines: ~34,055
- Blank/whitespace-only lines: ~33,961 (each ~80 spaces)
- Actual visible text: ~1.5 KB
So >99% of the body is whitespace. Converting this giant string with the Html2Text regex set is what pushes PCRE over its limit on hosts with default/low limits, producing the null and the crash. On a host with high PCRE limits the same body does not return null, but the conversion still degrades (it collapsed to an empty string in testing), so the underlying input is clearly pathological.
Anonymized reproduction snippet
A minimal version of the stored body (real one has thousands more blank lines in the signature <td>):
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<style type="text/css" style="display:none;"> P {margin-top:0;margin-bottom:0;} </style>
</head>
<body dir="ltr">
<div class="elementToProof" style="font-family: Aptos, Calibri, sans-serif; font-size: 12pt;">
Hallo zusammen, </div>
<div class="elementToProof" style="font-family: Aptos, Calibri, sans-serif;"><br></div>
<div class="elementToProof" style="font-family: Aptos, Calibri, sans-serif;">
[…short customer question, ~1.5 KB of real text…]</div>
<div class="elementToProof"><br></div>
<div class="elementToProof">Danke vorab und freundliche Grüße</div>
<div class="elementToProof">[Name removed]</div>
<table style="width: 740px; font-family: sans-serif; font-size: 10px;" border="0" cellspacing="0" cellpadding="0">
<tbody><tr><td align="left"><br>
<table width="740" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td width="150" align="center" valign="top">
<a href="https://example.com/" target="bold">
<img width="200" height="82" alt="Logo" src="https://helpdesk.example.com/storage/attachment/…?id=…&token=…" border="0">
</a>
</td>
<td valign="top" style="font-size: 9px;">
<span style="color: rgb(0,128,201); font-size: 14px; font-weight: bold;">[Name removed]</span><br>
<!-- ~34,000 blank whitespace-only lines like the ones below were stored here -->
<span>[Title removed]</span><br>
</td>
</tr></tbody></table>
</td></tr></tbody></table>
</body>
</html>
Steps to reproduce
- Receive an email from Outlook/Exchange with a heavy HTML signature that includes a large run of blank/whitespace lines (body ends up multi-MB).
- Open the conversation and reply to the customer.
- On a host with default/tight PCRE limits (PHP 8.1+), the send fails with the error above; the reply is not delivered.
Expected behaviour
The reply should send. htmlToText() (or the bundled Html2Text library) should not pass a null subject to preg_replace_callback(), and ideally should degrade gracefully when the PCRE engine fails on an oversized/whitespace-bloated body.
Suggested fix (for discussion)
Guard against preg_replace() returning null in the conversion path, e.g.:
- In
Html2Text::converter(), fall back to the previous value when preg_replace()/preg_replace_callback() returns null ($text = preg_replace(...) ?? $text;), and/or
- In
Helper::htmlToText(), detect a failed conversion and fall back to a strip_tags()/whitespace-collapsed version of the body.
Independently, it may be worth collapsing excessive runs of blank lines/whitespace when storing inbound HTML bodies, so multi-MB whitespace-only signatures don't get persisted verbatim.
Workaround that helped
Raising PCRE limits on the host (and disabling PCRE-JIT) stops the immediate crash on the affected server:
pcre.backtrack_limit = 10000000
pcre.recursion_limit = 10000000
pcre.jit = 0
This is only a mitigation — the unguarded null and the storage of multi-MB whitespace bodies remain.
PHP version: PHP 8.3.31
FreeScout version: 1.8.208
Database: Mysql (10.11.15-MariaDB-log)
Are you using CloudFlare: No
Are you using non-official modules: Yes
Summary
Sending a reply to a customer fails with a
Send error("Fehler senden") whenever the conversation contains an email whose stored HTML body is very large and consists almost entirely of blank/whitespace lines (a bloated Outlook/Exchange signature). The plain-text email view (emails/customer/reply_fancy_text.blade.php) runs the body throughHelper::htmlToText(), the underlyingHtml2Textconversion'spreg_replace()returnsnull, and thatnullis then passed straight intopreg_replace_callback(), which on PHP 8.1+ raises a deprecation that gets escalated to a hard error and aborts the send.Error message
Environment
html2text/html2textlibrary)pcre.backtrack_limit,pcre.recursion_limit,pcre.jit)Where it happens
resources/views/emails/customer/reply_fancy_text.blade.phprenders the body:{!! \Helper::htmlToText($thread->body, true) !!}Helper::htmlToText()(app/Misc/Helper.php) hands the body toHtml2Text, and inside the library (vendor/html2text/html2text/src/Html2Text.php,converter()):preg_replace()returnsnullwhen the PCRE engine hits a limit (e.g.PREG_BACKTRACK_LIMIT_ERROR,PREG_RECURSION_LIMIT_ERROR, or a JIT stack limit). The library does not check fornullbefore passing the value topreg_replace_callback()on the next line.Root cause of the pathological body
The triggering email is a corporate Outlook/Exchange message with a heavy HTML signature (logo + social icons + a large image). FreeScout stored the body verbatim, including a huge run of empty whitespace lines inside the signature
<table>:So >99% of the body is whitespace. Converting this giant string with the
Html2Textregex set is what pushes PCRE over its limit on hosts with default/low limits, producing thenulland the crash. On a host with high PCRE limits the same body does not returnnull, but the conversion still degrades (it collapsed to an empty string in testing), so the underlying input is clearly pathological.Anonymized reproduction snippet
A minimal version of the stored body (real one has thousands more blank lines in the signature
<td>):Steps to reproduce
Expected behaviour
The reply should send.
htmlToText()(or the bundledHtml2Textlibrary) should not pass anullsubject topreg_replace_callback(), and ideally should degrade gracefully when the PCRE engine fails on an oversized/whitespace-bloated body.Suggested fix (for discussion)
Guard against
preg_replace()returningnullin the conversion path, e.g.:Html2Text::converter(), fall back to the previous value whenpreg_replace()/preg_replace_callback()returnsnull($text = preg_replace(...) ?? $text;), and/orHelper::htmlToText(), detect a failed conversion and fall back to astrip_tags()/whitespace-collapsed version of the body.Independently, it may be worth collapsing excessive runs of blank lines/whitespace when storing inbound HTML bodies, so multi-MB whitespace-only signatures don't get persisted verbatim.
Workaround that helped
Raising PCRE limits on the host (and disabling PCRE-JIT) stops the immediate crash on the affected server:
This is only a mitigation — the unguarded
nulland the storage of multi-MB whitespace bodies remain.PHP version: PHP 8.3.31
FreeScout version: 1.8.208
Database: Mysql (10.11.15-MariaDB-log)
Are you using CloudFlare: No
Are you using non-official modules: Yes