osTicket Was Silently Dropping Reply Alerts — Here’s What Actually Broke

We recently ran into a frustrating and surprisingly difficult-to-diagnose osTicket issue involving:

  • Microsoft 365 OAuth IMAP inbound mail
  • AWS SES outbound SMTP
  • threaded customer replies
  • missing staff notifications

The weird part?

Everything appeared to work correctly.

Customer replies were successfully appended to existing tickets, but osTicket never generated “New Message Alert” notifications for staff or agents.

This post documents:

  • the symptoms
  • the debugging process
  • the actual root cause
  • and the source-code patch that fixed it.

Environment

Our stack looked like this:

ComponentConfiguration
osTicketv1.18.3
OSUbuntu 20/22
Web ServerApache
PHP8.2
Inbound MailMicrosoft 365 IMAP (OAuth2)
Outbound MailAWS SES SMTP

Important detail:

  • inbound and outbound email paths were split
  • the issue appeared after moving outbound mail to AWS SES

The Exact Symptom

This distinction is critical.

What worked

✅ IMAP mail fetching worked
✅ New tickets created correctly
✅ Customer replies appended to existing tickets
✅ AWS SES outbound SMTP worked
✅ New ticket alerts worked normally

What failed

❌ Staff “New Message Alert” emails were never sent for threaded customer replies

This created a particularly confusing scenario because:

  • replies appeared correctly inside the ticket
  • agents could see them if logged into osTicket
  • but nobody received email notifications

Initial Troubleshooting

We checked basically everything:

  • Department manager alerts
  • Assigned agent alerts
  • Last respondent alerts
  • Queue visibility
  • Team assignments
  • Department configuration
  • Mail fetching
  • SMTP delivery
  • Cron execution
  • osTicket templates
  • Database values
  • Apache logs
  • PHP syntax/runtime errors

Nothing obvious appeared broken.

Even worse:
there were no meaningful errors in either Apache logs or osTicket logs.


The Important Discovery

Eventually, we started adding debug logging directly into the osTicket source.

The breakthrough came while tracing logic through:

<code>include/class.thread.php
</code>

Specifically this section:

<code>'autorespond' =&gt; !isset($mailinfo&#91;'passive']),
</code>

We later discovered replies were being marked as:

<code>$mailinfo&#91;'passive'] = true;
</code>

due to:

<code>// Passive threading - listen mode
</code>

What “Passive Threading” Means

osTicket normally embeds identifiers into outbound emails.

When a customer replies:

  • osTicket decodes those identifiers
  • recognizes the ticket
  • processes the message normally

However, after moving outbound mail through AWS SES while still ingesting replies through Microsoft 365 IMAP, osTicket could no longer fully decode its own outbound identifiers consistently.

So osTicket fell back to:

<code>Passive threading
</code>

Passive threading still allowed replies to attach to the correct ticket using heuristics like:

  • subject matching
  • References headers
  • In-Reply-To headers

Which is why threading itself still worked.


The Real Bug

At first glance, passive threading looked like the problem.

But it actually wasn’t.

The real issue was that osTicket later reused:

<code>$autorespond = false;
</code>

to suppress BOTH:

  • customer autoresponses
  • staff alert notifications

Specifically this logic in:

<code>include/class.ticket.php
</code>
<code>if (!($alerts &amp;&amp; $autorespond))
    return $message;
</code>

Because passive threading forced:

<code>$autorespond = false;
</code>

staff notifications were silently skipped entirely.

So the chain became:

<code>Passive threading
→ autorespond = false
→ staff alerts skipped
</code>

Even though the incoming customer reply was legitimate.


The Fix

The solution was to separate:

  • autoresponder suppression
    from
  • staff notification suppression

We added a dedicated variable:

<code>$staffalerts = $alerts
    &amp;&amp; !(isset($vars&#91;'mailflags'])
        &amp;&amp; (!empty($vars&#91;'mailflags']&#91;'bounce'])
            || !empty($vars&#91;'mailflags']&#91;'auto-reply'])))
    &amp;&amp; !$message-&gt;isBounceOrAutoReply();
</code>

Then changed:

<code>if (!($alerts &amp;&amp; $autorespond))
    return $message;
</code>

to:

<code>if (!$staffalerts)
    return $message;
</code>

Why This Works

This preserves suppression for:

  • bounce messages
  • automatic replies
  • vacation responders
  • mail loops

while still allowing legitimate customer replies to generate:

  • staff notifications
  • agent alerts
  • “New Message Alert” emails

even when passive threading occurs.


Temporary Debug Logging

During troubleshooting, we added temporary logging like:

<code>error_log(date('c') . " OST DEBUG vars: "
    . json_encode($vars));
</code>

and monitored:

<code>tail -f /tmp/osticket-debug.log
</code>

This made it possible to trace:

  • mail flags
  • passive threading state
  • autorespond behavior
  • alert suppression logic

Once fixed, the debug logging was removed/commented out.


Important Caveat

This is a source-code modification to osTicket core files.

Future osTicket updates may overwrite the patch.

Before editing, make backups:

<code>cp include/class.ticket.php include/class.ticket.php.backup
</code>

and document the change somewhere internally.


Long-Term Considerations

Long-term, using Microsoft 365 for BOTH:

  • inbound
  • outbound

may avoid passive threading behavior entirely.

However, this patch fixes the actual logic flaw regardless of mail provider combination.

Even if passive threading occurs, legitimate customer replies should still generate staff alerts.


Final Thoughts

This was one of those issues where:

  • mail worked
  • threading worked
  • notifications partially worked
  • logs showed nothing useful

Yet an entire portion of the workflow silently failed.

If you’re running:

  • osTicket
  • Microsoft 365 OAuth IMAP
  • AWS SES SMTP

and seeing:

  • replies append correctly
  • but staff never receive “New Message Alert” emails

check whether passive threading is suppressing alerts through the shared $autorespond logic.

That ended up being the real culprit.