fix(messaging): keep a single SMTP recipient in the To header by HarshMN2345 · Pull Request #13534 · appwrite/appwrite · GitHub
Skip to content

fix(messaging): keep a single SMTP recipient in the To header - #13534

Merged
HarshMN2345 merged 11 commits into
mainfrom
fix/messaging-smtp-to-header
Sep 21, 2026
Merged

HarshMN2345 merged 11 commits into
mainfrom
fix/messaging-smtp-to-header

Conversation

@HarshMN2345

@HarshMN2345 HarshMN2345 commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Closes #12365
Closes #10474

What does this PR do?

An SMTP send moved every recipient into BCC and left To empty, so a message to a single recipient arrived with no To header at all and clients showed no recipient (#12365). The BCC move itself is deliberate — a batch is one MIME message, and topic subscribers must not see each other (#10474) — but it was applied unconditionally, including when there was nobody to hide the recipient from.

A recipient now keeps a visible To header when, and only when, no other address shares the message. Every other shape moves to BCC exactly as before.

The condition is on the message's addressing rather than on the size of the batch. Batch size is a transport artifact: array_chunk() splits on the adapter's per-request limit, recipients are grouped per provider, and retrySend() narrows a batch to whoever is still failing. Keying off it would let a topic batch that happened to collapse to one recipient write that address into the To header of a message the cc and bcc addresses also read, and would hide 1000 subscribers of a 1001-subscriber topic while disclosing the last one.

Multi-recipient batches still carry no To header at all. #12365 and #10474 pull in opposite directions here and that is the side this keeps: a placeholder address in To is also an SMTP envelope recipient and would receive a copy of every batch, and the message API has no way to express the undisclosed-recipients:; group form.

Test Plan

testSendEmailSmtpHeaders sends three messages through the existing Maildev fixture, one per shape the worker distinguishes:

  • a lone recipient keeps To — fails on main;
  • a single topic subscriber sharing the message with a cc recipient does not — fails against a rule keyed on batch size;
  • several topic subscribers do not — pins BCC in email #10474.

composer lint and PHPStan pass on both changed files. Full Messaging E2E runs in CI.

Delivery accounting and repeated CC/BCC delivery across batches are unchanged.

Every SMTP send moved its recipients into BCC and emptied the To list. The
default recipient the comment promised was dropped when the messaging Email
message stopped taking one, so nothing was left to occupy the field, and the
renderer omits a header whose value renders empty. Mail arrived with no To at
all, which clients show as a missing recipient and some MTAs score as spam.

Hiding recipients from each other only matters when there is more than one, so
restrict the rewrite to batches. A lone recipient keeps a real To header, and
multi-recipient sends still disclose nothing.

No placeholder is put back in To: the SMTP envelope unions to, cc and bcc into
the forward paths, so any address there would receive a copy of every batch and
be counted as delivered.
@greptile-apps

greptile-apps Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Comment thread tests/e2e/Services/Messaging/MessagingBase.php Outdated
@github-actions

github-actions Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

✨ Benchmark results

Comparing main (before) → fix/messaging-smtp-to-header (after).

Metric Before After Change
🚀 Requests/sec 180.24 198.94 🟢 +10.4%
⏱️ Latency P50 96.9 ms 87.64 ms 🟢 -9.6%
⏱️ Latency P95 226.16 ms 200.49 ms 🟢 -11.4%
Per-scenario breakdown & investigation details

Metrics below reflect the current branch (after). Δ P95 compares against the base.

Scenario P50 (ms) P95 (ms) Requests RPS Δ P95 (ms)
API total 87.64 200.49 12,540 198.94 -25.67
Account 159.88 300.97 660 11.1 -58.26
TablesDB 85.29 162.73 6,820 110.78 -15.43
Storage 79.9 176.86 3,300 55.22 -11.71
Functions 119.69 233 1,760 30.09 -41.53

Top API waits (after)

API request Max wait (ms)
account.name.update 456.06
tablesdb.rows.increment 424.37
storage.buckets.create 388.11
account.prefs.update 385.17
tablesdb.rows.delete 379.91

A target created automatically alongside the user carries no provider id, and
the worker then delivers it through whichever enabled email provider it finds
first - which other tests in this suite also create. Create the target bound to
the SMTP provider, on its own address so it does not collide with the automatic
one.
The e2e mail container runs with MAILDEV_INCOMING_USER and _PASS set, so a
provider without credentials is refused and the message ends up failed.
Comment thread tests/e2e/Services/Messaging/MessagingBase.php Outdated
Comment thread src/Appwrite/Platform/Workers/Messaging.php Outdated
HarshMN2345 and others added 7 commits September 21, 2026 17:01
…the message

Batch size decided whether recipients stayed visible, but it is a transport
artifact: array_chunk() splits on the adapter's per-request limit, recipients
are grouped per provider, and retrySend() narrows the batch to whoever is still
failing. A topic batch that collapsed to one recipient therefore wrote that
address into the To header of a message the cc and bcc addresses also read, and
a 1001-subscriber topic hid 1000 subscribers while disclosing the last one.

Gate on whether anyone else is on the envelope instead. A lone recipient with no
cc or bcc keeps a visible To header; every other shape moves to BCC as before.
This also drops the array_diff(), which only ever subtracted the message's
explicit bcc list and so left recipients hidden by this same branch exposed when
a retry narrowed the batch to one of them.
The suite asserted a visible To header for a lone recipient, then re-sent to a
topic whose only subscriber was also on the message's bcc list. That second send
produced the same headers before and after the fix, so it pinned nothing, and
the bcc assertion could not fail at all: bcc travels in the SMTP envelope and is
never written as a header.

Send three messages instead, one per shape the worker distinguishes: a lone
recipient keeps To, a single subscriber sharing the message with a cc recipient
does not, and several subscribers do not. The middle case fails without the
worker change.

Read the SMTP credentials from _APP_SMTP_* and delete the provider afterwards,
as testSendEmailEncryptedAttachment does, and drop that test's comment about
SMTP carrying no To header, which no longer holds for its single recipient.
@HarshMN2345
HarshMN2345 merged commit 03ab459 into main Sep 21, 2026
9 checks passed
@HarshMN2345
HarshMN2345 deleted the fix/messaging-smtp-to-header branch September 21, 2026 15:07
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.

Emails sent through custom SMTP provider are missing the "To" header BCC in email

2 participants