You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SMTP proxy sends raw RFC 5322 recipients, which the public API schema rejects (invalid_string on "to") #439
Every message relayed through usesend/smtp-proxy whose To header carries a display name is rejected by useSend own public API, so it never leaves the proxy.
Cause
apps/smtp-server/src/server.ts (current main, bd83535) forwards the address headers in their raw RFC 5322 form:
to: Array.isArray(parsed.to)
? parsed.to.map((addr)=>addr.text).join(", ")
: parsed.to?.text,
parsed.to.text is "Gabriel Ferreira" <gabriel@example.com>, but apps/web/src/server/public-api/schemas/email-schema.ts validates recipients as bare addresses:
constemailAddress=z.string().email().max(320);constrecipients=emailAddress.or(z.array(emailAddress).min(1).max(50));// to: recipients
from is unaffected because its schema is z.string().min(3).max(512), free-form. That asymmetry is why the error names only to.
The .join(", ") branch is broken for the same reason: a comma-joined string is not a single valid address either, so two plain recipients also fail. Same applies to cc/bcc on main.
Reproduction
Point any SMTP client that sets a display name (Documenso, Plane CE, Penpot, most mail clients) at the proxy and send one message. Proxy log:
Sending email to useSend API at: http://usesend:3000/api/v1/emails
useSend API error response: error: "{\"success\":false,\"error\":{\"issues\":[{\"validation\":\"email\",\"code\":\"invalid_string\",\"message\":\"Invalid email\",\"path\":[\"to\"]}]}}"
email data: {"to":"\"Gabriel da Silva Ferreira\" <gabriel@quartzo.ai>","from":"\"Quartzo Docs\" <noreply@quartzo.ai>", ...}
The SMTP transaction then fails, so the sending app sees a delivery error and no Email row is ever created.
Suggested fix
Use mailparser .value (the parsed { name, address } list) instead of .text, and send an array — the schema already accepts z.array(emailAddress):
functiontoAddressList(field): string[]|undefined{if(!field)returnundefined;constgroups=Array.isArray(field) ? field : [field];constaddresses=groups.flatMap((g)=>g?.value??[]).map((e)=>e?.address).filter(Boolean);returnaddresses.length>0 ? addresses : undefined;}
Applied to to, cc, bcc and replyTo; from should keep .text so the display name survives.
Happy to send a PR if that shape looks right. We are running this patch in a fork today and mail flows.
Activity