Skip to content

fix: stop mail loops from bounces and autoresponders - #3628

Open
aerodeval wants to merge 1 commit into
frappe:developfrom
aerodeval:fix/bounce-notification-loop
Open

fix: stop mail loops from bounces and autoresponders#3628
aerodeval wants to merge 1 commit into
frappe:developfrom
aerodeval:fix/bounce-notification-loop

Conversation

@aerodeval

Copy link
Copy Markdown
Member

What

Inbound mail is only screened with the X-Auto-Generated header, which nothing but helpdesk's own acknowledgement emails set. Real bounces and out-of-office replies pass straight through into ticket creation, where they can start a mail loop with the sending MTA.

Once a machine-generated mail becomes a ticket or a communication, three separate paths can reply to it:

  1. New ticket ackHD Ticket.after_insert sends the acknowledgement to raised_by, i.e. the address that just bounced.
  2. Portal ticket threading — when the mail threads onto a ticket created from the portal, frappe CCs the parent doc's owner (mail_cc) on every inbound mail — the same dead address.
  3. enable_auto_reply — an account with auto-reply on answers mailer-daemon directly.

Each reply bounces again, which creates another inbound mail, and so on.

Changes

helpdesk/overrides/email_account.py — new auto_generated_reason(msg) identifies machine-generated mail by the standard markers instead of relying on our own header:

  • RFC 3834 Auto-Submitted — anything but no (parameters after the value are tolerated, e.g. no; owner=...)
  • RFC 3464 multipart/report; report-type=delivery-status — catches a DSN even where no MTA added a Return-Path (POP3, Frappe Mail)
  • RFC 5321 §6.1 null return-path (<>) — which bounces MUST carry
  • X-Auto-Generated is kept, so our own acks are still caught

Matches now go to handle_bad_emails(uid, message, reason) and land in Unhandled Email with the reason recorded, instead of being silently dropped by a bare continue. The fetch has already marked them seen either way, so this costs nothing and leaves a trace when a real mail is misclassified.

Testing

helpdesk/overrides/test_email_account.py — 8 unit tests over auto_generated_reason with real message fixtures (Gmail DSN, out-of-office, a DSN with no Return-Path, and genuine customer replies), asserting bounces are dropped and human mail is kept, including the Auto-Submitted: no cases.

Note

These are pure stdlib email tests with no site fixtures. They currently need frappe importable, since the module imports it at the top; the function itself touches none of it.

Inbound mail was only screened with the X-Auto-Generated header, which
only helpdesk's own acks set. A real bounce or out-of-office therefore
reached ticket creation and could start three separate loops: the ack in
HD Ticket.after_insert replies to raised_by (the address that just
bounced), threading onto a portal ticket makes frappe CC the parent doc's
owner on every inbound mail, and an account with enable_auto_reply
answers mailer-daemon directly.

Detect machine-generated mail by the standard markers instead: RFC 3834
Auto-Submitted, the RFC 3464 multipart/report delivery-status type, and
the RFC 5321 null return-path. Matches are routed to handle_bad_emails so
they land in Unhandled Email rather than disappearing -- the fetch has
already marked them seen either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.36170% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.93%. Comparing base (04ac2c4) to head (253623c).
⚠️ Report is 73 commits behind head on develop.

Files with missing lines Patch % Lines
helpdesk/overrides/email_account.py 71.42% 4 Missing ⚠️
helpdesk/overrides/test_email_account.py 96.96% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3628      +/-   ##
===========================================
+ Coverage    68.57%   68.93%   +0.36%     
===========================================
  Files          140      144       +4     
  Lines         9181     9442     +261     
===========================================
+ Hits          6296     6509     +213     
- Misses        2885     2933      +48     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RitvikSardana
RitvikSardana marked this pull request as ready for review July 30, 2026 10:53
@mergify

mergify Bot commented Jul 30, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

The classifier preserves ordinary and explicitly human-submitted mail while diverting recognized automated messages through the existing per-message error-handling path.

Reviews (1): Last reviewed commit: "fix: stop mail loops from bounces and au..." | Re-trigger Greptile

@aerodeval

Copy link
Copy Markdown
Member Author

@RitvikSardana have to test one more thing, do not merge yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants