feat: add name support to email 'to' field, matching cc/bcc format#114
feat: add name support to email 'to' field, matching cc/bcc format#114ChiragAgg5k merged 8 commits intomainfrom
Conversation
Introduce a Recipient class to replace raw string arrays for to, cc, and bcc fields. This provides a consistent, type-safe API for associating names with email addresses across all fields and adapters. BREAKING CHANGE: Email constructor now accepts array<Recipient> for to, cc, and bcc instead of array<string> and array<array<string,string>>.
Greptile SummaryThis PR extends the
Confidence Score: 4/5Safe to merge after fixing the Mailgun Reply-To header key bug on line 68. One P1 finding remains: the Mailgun src/Utopia/Messaging/Adapter/Email/Mailgun.php — line 68 has a pre-existing bug (missing Important Files Changed
Reviews (5): Last reviewed commit: "chore: bump PHP requirement to 8.1+, use..." | Re-trigger Greptile |
Update MailgunTest, SendgridTest, ResendTest, and SMTPTest to construct Recipient objects instead of raw strings/arrays. Also fix PHPStan warning in Resend adapter for always-truthy empty() check.
- Add email validation in Recipient constructor to reject empty strings - Fix Mailgun address format to use RFC 5322 compliant "Name <email>" with a space before the angle bracket, consistent with Resend adapter
The Email constructor now accepts strings, associative arrays, or Recipient objects for to, cc, and bcc fields. Inputs are normalized to Recipient objects internally so adapters remain unchanged. This preserves full backward compatibility with the existing API while adding support for named recipients via the new Recipient class.
Replace the Recipient value object with a simpler approach: the to field now uses the same array<array<string,string>> format as cc and bcc. Plain strings are auto-normalized to ['email' => $string] in the Email constructor, preserving full backward compatibility.
- Unify normalization: cc/bcc now go through normalizeRecipient just like to, so all three fields accept both plain strings and arrays - Reject empty email strings in normalizeRecipient (was only checking isset, which passes for empty strings) - Remove separate validateRecipients method (redundant with unified normalizeRecipient) - Fix Mailgun from/reply-to format to use RFC 5322 space before < - Fix SMTP null guard on getCC()/getBCC() in result-reporting loop - Cache getTo() result in Mailgun to avoid redundant calls - Add tests for named to recipients, mixed string/array inputs, cc/bcc string normalization, and validation rejection of empty emails and missing email keys
The test sends 2 recipients so it can't use assertResponse which hardcodes deliveredTo === 1. Assert the count and status directly.
Raise minimum PHP version from 8.0 to 8.1 in composer.json and use first-class callable syntax (self::method(...)) for array_map calls in the Email constructor.
| 'text' => $message->isHtml() ? null : $message->getContent(), | ||
| 'html' => $message->isHtml() ? $message->getContent() : null, | ||
| 'h:Reply-To: '."{$message->getReplyToName()}<{$message->getReplyToEmail()}>", | ||
| 'h:Reply-To: '."{$message->getReplyToName()} <{$message->getReplyToEmail()}>", |
There was a problem hiding this comment.
h:Reply-To entry is a numeric-indexed value, not a keyed header
The array entry on this line is missing the => assignment operator, so the entire string 'h:Reply-To: Name <email>' is stored as a numeric-indexed element (e.g. $body[0]). When form-encoded and sent to Mailgun this becomes 0=h%3AReply-To%3A+... rather than the h:Reply-To custom-header field Mailgun expects. The Reply-To header is therefore silently never forwarded.
This is a pre-existing bug, but this PR already touches the line (adding the RFC 5322 space), making it the natural place to fix it:
| 'h:Reply-To: '."{$message->getReplyToName()} <{$message->getReplyToEmail()}>", | |
| 'h:Reply-To' => "{$message->getReplyToName()} <{$message->getReplyToEmail()}>", |
Summary
tofield on theEmailmessage class to accept the samearray<array<string,string>>format asccandbcc, enabling recipient names (e.g.['email' => '...', 'name' => '...'])['alice@example.com']are auto-normalized to[['email' => 'alice@example.com']]in the constructortoentries via array access, formatting named recipients as"Name <email>"where supported"Name <email>"(with space before angle bracket)Usage
Test plan
torecipients correctly (e.g."Alice <alice@example.com>")emailkey