Skip to content

Let callers choose the sender address on every send path - #21

Merged
jgalea merged 2 commits into
mainfrom
feat/from-address
Jul 9, 2026
Merged

jgalea merged 2 commits into
mainfrom
feat/from-address

Conversation

@jgalea

@jgalea jgalea commented Jul 9, 2026

Copy link
Copy Markdown
Owner

This PR adds a from parameter to send_email, reply_email, forward_email, create_draft, and update_draft, so an account that owns several addresses can pick which one it speaks as.

buildRawMimeMessage already accepted a from. Nothing ever passed it. The Gmail path built its MIME message without a From header at all, so Gmail filled in the primary address; IMAP and JMAP hardcoded the account address. An alias was unreachable through the API.

The sender is validated before the message goes out. This matters more than it looks: when the From header names an alias you haven't verified, Gmail does not error, it quietly rewrites the header to your primary address and reports success. Sending as the wrong identity is indistinguishable from sending correctly. Gmail now checks the address against settings.sendAs.list (refusing pending aliases) and JMAP against Identity/get, each failing with the addresses that would have worked. IMAP has no alias list to consult, so from goes to the SMTP relay, which accepts or rejects it at send time.

JMAP had a related gap: EmailSubmission/set never carried an identityId, leaving the server to infer the sender, which is what made a non-default from impossible there. The resolved identity's id is now attached, and urn:ietf:params:jmap:submission was added to the using list where it always belonged.

Accepts alias@example.com or Name <alias@example.com>, matched case-insensitively. Omitting from preserves today's behavior exactly, and skips the alias lookup entirely.

The second commit is unrelated cleanup: the README auth examples and three test fixtures held credential-shaped placeholders that trip secret scanners. The security tests need those exact strings to prove redaction works, so they are assembled at runtime instead of being weakened.

Verified against a live Gmail account: a verified alias is accepted, display-name and mixed-case forms normalize to it, and an unlisted address is rejected with the permitted list. 243 tests pass, tsc is clean.

jgalea added 2 commits July 9, 2026 15:31
send_email, reply_email, forward_email, create_draft and update_draft
now take a from parameter. buildRawMimeMessage already accepted from;
the Gmail path never passed it and IMAP/JMAP hardcoded the account
address, so an account with several aliases could only ever send as
its primary.

Validate before sending. Gmail rewrites an unverified From to the
primary address without erroring, so the wrong sender looks like a
successful send; check against the send-as list instead, and against
Identity/get on JMAP. IMAP has no alias list, so the relay decides.

JMAP submissions now carry the resolved identityId, and declare the
submission capability they always needed.
The README auth examples and three test fixtures carried literal
credential-shaped strings: password="your-app-password", a sample JWT,
and /home/user paths. All placeholders, all noisy on every scan.

The security tests need those exact inputs to prove redaction works, so
assemble them at runtime rather than weakening the assertions. The
README now uses <app-password>, which reads more clearly as a
placeholder anyway.
@jgalea
jgalea merged commit 60a1ac3 into main Jul 9, 2026
1 check passed
@jgalea
jgalea deleted the feat/from-address branch July 9, 2026 13:39
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.

1 participant