graph: use replyTo instead of from to avoid ErrorSendAsDenied
This commit is contained in:
@@ -115,10 +115,18 @@ export const graphAdapter =
|
||||
return { error: 'Graph is not configured (missing env vars).', sent: false }
|
||||
}
|
||||
|
||||
// From-display comes from the panel; falls back to the caller's from.
|
||||
// The panel's from-address is used as Reply-To, NOT as the message From.
|
||||
//
|
||||
// Why: app-only Graph sends from GRAPH_SENDER's mailbox. If we also set a
|
||||
// `from` that differs from that mailbox, Exchange demands "Send As"
|
||||
// permission on it and rejects with ErrorSendAsDenied otherwise. So we
|
||||
// never override `from` — Graph stamps the mail as GRAPH_SENDER (the
|
||||
// mailbox we legitimately own) — and route replies to the panel address
|
||||
// via Reply-To. Recipients see the mail from forms@… but replying reaches
|
||||
// the real destination. No Send-As needed.
|
||||
const panel = await getSiteIntegrations<GraphIntegrations>(payload)
|
||||
const fromAddress = panel.smtpFromAddress || undefined
|
||||
const fromName = panel.smtpFromName || undefined
|
||||
const replyToAddress = panel.smtpFromAddress || undefined
|
||||
const replyToName = panel.smtpFromName || undefined
|
||||
|
||||
const to = toRecipients(message.to)
|
||||
if (to.length === 0) {
|
||||
@@ -130,25 +138,28 @@ export const graphAdapter =
|
||||
const isHtml = typeof message.html === 'string' && message.html.length > 0
|
||||
const content = isHtml ? String(message.html) : String(message.text ?? '')
|
||||
|
||||
// Reply-To: prefer whatever the caller set; otherwise the panel address.
|
||||
const replyTo = message.replyTo
|
||||
? toRecipients(message.replyTo as SendEmailOptions['to'])
|
||||
: replyToAddress
|
||||
? [
|
||||
{
|
||||
emailAddress: {
|
||||
address: replyToAddress,
|
||||
...(replyToName ? { name: replyToName } : {}),
|
||||
},
|
||||
},
|
||||
]
|
||||
: []
|
||||
|
||||
const graphMessage: Record<string, unknown> = {
|
||||
body: { content, contentType: isHtml ? 'HTML' : 'Text' },
|
||||
subject: message.subject ?? '',
|
||||
toRecipients: to,
|
||||
...(message.cc ? { ccRecipients: toRecipients(message.cc) } : {}),
|
||||
...(message.bcc ? { bccRecipients: toRecipients(message.bcc) } : {}),
|
||||
// from is only honoured if the app has Send-As for that address; when
|
||||
// it's the shared mailbox itself, omit it and Graph uses the sender.
|
||||
...(fromAddress
|
||||
? {
|
||||
from: {
|
||||
emailAddress: { address: fromAddress, ...(fromName ? { name: fromName } : {}) },
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
// replyTo lets the recipient reply to the real submitter if the caller set it.
|
||||
...(message.replyTo
|
||||
? { replyTo: toRecipients(message.replyTo as SendEmailOptions['to']) }
|
||||
: {}),
|
||||
// NO `from` — Graph uses GRAPH_SENDER's own mailbox, so no Send-As.
|
||||
...(replyTo.length > 0 ? { replyTo } : {}),
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user