{{ message }}
Repository navigation
fix(messaging): skip mail input no provider can deliver instead of retrying - #13831
Merged
Merged
Conversation
Contributor
✨ Benchmark resultsComparing
Per-scenario breakdown & investigation detailsMetrics below reflect the current branch (after). Δ P95 compares against the base.
Top API waits (after)
|
Member
Author
|
@greptile review |
…stead of failing it
Member
Author
…message-type rejection
ArnabChatterjee20k
approved these changes
Sep 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
Resend answered
Invalidtofieldfor 269 cloud mails in two weeks (CLOUD-3QX0). Every one reached the provider, came back as a genericError sending mail: …with code 500, was retried by the queue, and landed in Sentry as an error, though nothing about the input could change.utopia-php/messaging2.4.0 (utopia-php/monorepo#321) now refuses such input withUtopia\Messaging\Exception\InvalidArgumentException, whosegetType()names the reason andgetValue()carries the offending input. This PR takes that release and handles the exception where mail is sent.The 500s this removes
Each scenario below is a real shape from the Sentry sample. Before, all of them took the same path in the Mails worker:
1. Recipient domain that cannot receive mail (
c.c,a.i,gyung.me976153, about 8% of events). A user signs up with a junk address and triggers a session alert or OTP. The message now fails to build before any request is made:2. Display name that breaks the address header (
gmail.comcases, real users). A name such asDoe, John <JD>was interpolated unquoted intoName <email>, so the provider saw a malformedto. The adapter now quotes it per RFC 5322 and the mail is delivered:A name carrying a line break, which would inject a header, is refused at construction with type
name_malformed.3. Reserved documentation domains (
example.com, about 90% of events). Developers testing with fake users. Resend blocks these by policy with a 422. The adapter maps that to the typed exception instead of a delivery failure, and the worker skips it:4. Malformed sender or reply-to from a project's custom SMTP options, previously a 500 on every mail for that project until someone noticed. Now
sender_malformedat construction.What changes in the workers
Workers\Mailsmoves message construction into the try and adds one catch ahead of the generic one. A skipped mail is recorded on the span and the job completes, so the queue does not redeliver it and Sentry does not see it:Workers\Notificationsdoes the same for the email channel, returningnullso no alert row is persisted for a mail that never went out:Workers\Messaging(the Messages API) builds one provider message per batch, so one undeliverable address must not cost the rest their send. The build now runs in a loop that records the offending recipient as a terminal error, drops it, and rebuilds for the others:A typed exception thrown by the send itself (a provider 422) is terminal regardless of what
isRetryableError()would say about its text.Tests
MailsTest: a recipient atjohn@c.cnever reaches the adapter and the worker returns; an adapter throwingprovider_rejectedis not retried and does not throw.NotificationsTest: an undeliverable address never reaches SMTP and leaves nonotificationsrow.AppwriteTest(push adapter): the base adapter's message-type rejection is now the typed exception, asserted by class rather than message text.The new worker tests error against the previous worker code. There is no unit seam for
Messaging::retrySend()without a database, so that change is covered by the messaging package's own tests and by review.