Skip to content

Let a transactional send set its own sender name - #204

Merged
s3rot0n1n merged 1 commit into
mainfrom
feat/transactional-from-name
Nov 15, 2026
Merged

s3rot0n1n merged 1 commit into
mainfrom
feat/transactional-from-name

Conversation

@clbotdev

Copy link
Copy Markdown
Contributor

Why

An integrator that sends mail for many of its own customers through one SendLit team can only use the team's sender name today. CourseLit sends every school's learner mail (sign-in codes, receipts, notifications) through shared platform teams, so all of it currently goes out as "CourseLit" instead of the school's name.

What changes

  • POST /emails and the send_email MCP tool take an optional fromName, used as the From name for that email instead of the delivery source's.
  • Only the name changes. The address stays the delivery source's verified one, so SPF, DKIM and DMARC are unaffected.
  • getEmailFrom now quotes a display name that holds RFC 5322 specials (a comma, a period, quotes and so on). Before, a name such as Smith, J. produced a broken From header. This also applies to team sender names.
  • The docs (developers/transactional.mdx), the OpenAPI spec and a changeset for the sendlit SDK are updated.

Security

A caller-chosen display name is a phishing risk, so fromName is refused when it holds:

  • control characters (\p{Cc}), including CR/LF, so no header injection;
  • invisible formatting characters (\p{Cf}), such as right-to-left overrides and zero-width spaces, which can make a name read as something else;
  • <, > or @, so the name can't pass for an address (support@paypal.com).

It is limited to 100 characters. The REST schema checks it, and createTransactionalEmail checks it again for MCP callers (invalid_from_name).

Display-name spoofing from a team's own verified domain stays possible, as it already is through the team sender name in settings and on any ESP that allows custom names.

Tests

  • utils/mail.test.ts: quoting.
  • transactional/queries.test.ts: the name is used with the pinned address and trimmed, and the cases above are refused without queueing.
  • transactional/queries.test.ts, mcp/tools/transactional.test.ts, routes-purpose.test.ts and each MCP test file pass; the API, contract and SDK typechecks and check:drift pass.

Consumer

CourseLit already sends fromName on learner mail (codelitdev/courselit#881). Until this ships, SendLit drops the unknown field and the mail keeps going out as "CourseLit".

POST /emails and the send_email MCP tool take an optional fromName, the From name for that
email instead of the delivery source's. Only the name changes: the address stays the delivery
source's verified one.

So the name can't break the From header or pass for an address, it is refused when it holds
control characters, invisible formatting characters (such as right-to-left overrides and
zero-width spaces), <, > or @. A name with RFC 5322 specials such as a comma is now quoted in
the From header, which also fixes such names on teams.
@s3rot0n1n
s3rot0n1n merged commit f3b62ea into main Nov 15, 2026
6 of 7 checks passed
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.

2 participants