fix(email-core): parse name-address strings in cc/to compose inputs#64
Merged
stevenobiajulu merged 1 commit intomainfrom Apr 29, 2026
Merged
fix(email-core): parse name-address strings in cc/to compose inputs#64stevenobiajulu merged 1 commit intomainfrom
stevenobiajulu merged 1 commit intomainfrom
Conversation
read_email returns recipients in 'Name <email>' form (RFC 5322
name-address), but compose actions wrapped each input string blindly as
{email: raw_string}. The whole 'Name <email>' string ended up in
EmailAddress.email, producing malformed Cc:/To: headers in Gmail (silent
recipient drop) and corrupt emailAddress.address in Microsoft Graph.
This change parses recipient strings once per action via a new
parseAddressString helper, then feeds the parsed EmailAddress[] to both
the allowlist check and the provider payload. Returning a structured
INVALID_ADDRESS error early avoids retrying parse failures inside
withRetry and avoids zod schema-validation pitfalls in the MCP layer.
Notable scope adjustments after peer review:
- send.ts allowlist now receives parsed bare emails — without this,
passing 'Alice <alice@allowed.com>' as 'to' would be falsely rejected.
- update_draft preserves cc:[] explicit-clear semantics.
- Gmail provider test extended to lock in 'Cc: \"Name\" <email>' form.
Out of scope (separate follow-ups): cc-allowlist on send_email/
reply_to_email, list_emails schema for to/cc, MS Graph sibling bug.
Observed 2026-04-24.
Fixes #47.
|
|
||
| // Name-address: either "quoted name" + <email>, or bare-name + <email>. | ||
| // Quotes must be balanced when present; bare name cannot contain '<' or '"'. | ||
| const m = /^\s*(?:"([^"]*)"|([^<"]*?))\s*<\s*([^\s"<>@]+@[^\s"<>@]+)\s*>\s*$/.exec(trimmed); |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Merged
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.
Summary
read_emailreturns recipients in"Name <email>"form, but the compose actions (reply_to_email,send_email,create_draft,update_draft) wrapped each input string blindly as{email: rawString}. The full"Name <email>"string ended up inEmailAddress.email, producing malformedCc:/To:headers in Gmail (silent recipient drop) and corruptemailAddress.addressin Microsoft Graph.Fixes #47.
What changed
packages/email-core/src/utils/address.tswithparseAddressString+parseAddressList. Narrow on purpose — bare email,Name <email>,"Quoted, Name" <email>,<email>. Rejects multi-address strings (Alice <a@x>, Bob <b@y>), unbalanced quotes, and stray angle brackets.parseRecipientshelper incompose-helpers.tsthat returns{to, cc}parsed or a structuredINVALID_ADDRESSerror.reply.ts,send.ts,draft.tsparse input recipients once at the top ofrun()and feed the parsedEmailAddress[]to both the allowlist check and the provider payload.send.ts's allowlist now receivesparsed.to.map(a => a.email)instead of raw strings — without this, passingto: "Alice <alice@allowed.com>"would be falsely rejected as not allowlisted.update_draftpreserves thecc: []explicit-clear semantics.Total non-test diff: ~80 lines.
Scope notes (after peer review)
The minimal "swap the mapper" version of this fix would have introduced a regression in
send_email's allowlist enforcement. Both Gemini and Codex peer reviews flagged this and a related concern that throwing parse errors insidewithRetrywould cause spurious retries. The implemented version parses early and returns structuredINVALID_ADDRESSbefore either allowlist or retry logic.Out of scope (follow-ups)
ccis never explicitly allowlisted onsend_email. This is a pre-existing concern that becomes more visible once parsing works correctly. Will file a separate issue.list_emails/search_emailsschemas don't exposeto/cc. Separate change.createDraft()dropsccandbccrecipients #48) intentionally untouched.Test plan
npm run test:run— all 581 tests pass across 4 packagesnpm run lint --workspaces).map(email => ({ email }))call sites in sourceto, structuredINVALID_ADDRESSerrors with field/index,update_draft cc: []explicit-clear, GmailCc: "Name" <email>serializer assertionObserved 2026-04-24. Do not merge — please review.