Skip to content

fix(smtp): respect configured SMTP user for XOAUTH2 authentication - #13668

Open
Rikdekker wants to merge 1 commit into
nextcloud:mainfrom
Rikdekker:fix/smtp-xoauth2-outbound-user
Open

fix(smtp): respect configured SMTP user for XOAUTH2 authentication#13668
Rikdekker wants to merge 1 commit into
nextcloud:mainfrom
Rikdekker:fix/smtp-xoauth2-outbound-user

Conversation

@Rikdekker

Copy link
Copy Markdown

Fixes #13667

Summary

For xoauth2 accounts the SASL XOAUTH2 identity used for SMTP was hardcoded to
$account->getEmail(), so the configured SMTP user had no effect. This makes it
impossible to authenticate as one mailbox and send as another — which is exactly what
Microsoft 365 requires for shared mailboxes.

This PR uses getOutboundUser() when set and falls back to the account's email address
otherwise.

Why

Exchange Online accepts a shared mailbox as the XOAUTH2 identity for IMAP but rejects it
for SMTP AUTH with 535 5.7.3 Authentication unsuccessful, because a shared mailbox has
no sign-in credentials of its own. A Microsoft engineer confirms this in
this Q&A thread:
"we cannot use shared mailbox for SMTP AUTH client submission."

The supported pattern is to authenticate as the delegate's own mailbox and let Exchange
Send As handle the sender address. Thunderbird works this way, which is why users report
that Thunderbird can send from a shared mailbox while Mail cannot.

Why only SMTP

IMAPClientFactory deliberately keeps using the account email: for IMAP the shared
mailbox address is the correct identity, and Microsoft documents that substitution.
That path works today and is untouched.

Backwards compatibility

No behaviour change for existing accounts:

  • AccountForm.vue requires smtpUser in the OAuth flow as well (only the password
    fields are gated behind !useOauth) and pre-fills it with the email address.
  • Autoconfig fills it from smtpConfig.username; provisioning computes it via
    buildSmtpUser().
  • The submit payload deliberately keeps smtpUser for OAuth accounts.

So for accounts created through the UI both values are already identical, and the
fallback covers CLI/provisioning cases where the column may be empty.

Precedent

#12442 (Dovecot master user) introduced the same concept — an SMTP auth identity distinct
from the account address — across the IMAP, SMTP and Sieve factories.

Tests

tests/Unit/SMTP/SmtpClientFactoryTest.php had no xoauth2 coverage at all. This PR adds
two cases:

  • SMTP user set and different from the email address → used as XOAUTH2 identity
  • SMTP user empty → falls back to the email address

Verified locally against a Nextcloud instance (PHP 8.5):

OK (3 tests, 11 assertions)

Reverting the production change makes the new test fail with exactly the reported
symptom, confirming the tests are meaningful:

-    'username' => 'user@domain.tld'
+    'username' => 'shared@domain.tld'

End-to-end verification against a real Microsoft 365 tenant

Verified with an actual Exchange Online shared mailbox (shared@example.org), with Full Access and
Send As granted to the personal account (user@example.org), configured exactly as users do today:
the shared mailbox added as its own account, authorised with the personal user's OAuth
token.

Step XOAUTH2 identity Result
IMAP (read) shared@example.org OK — 11 mailboxes listed
SMTP, before fix (smtp_user = account address) shared@example.org Server denied authentication
SMTP, after fix (smtp_user = user@example.org) user@example.org Login OK
Actually sending From: shared@example.org user@example.org Message delivered
Counter-test: revert smtp_user to shared@example.org shared@example.org Denied again

So the same account, token and mailbox only differ in the SMTP auth identity, and that is
what decides between rejection and delivery. Sending as the shared mailbox works through
Exchange Send As once the identity is decoupled — which is what this PR enables.

The sent message is stored in the shared mailbox's own Sent Items folder (verified over
IMAP), so it is visible to everyone with access to that mailbox. That is a meaningful
difference from the alias-based workaround, where the copy ends up in the sender's
personal Sent folder and colleagues sharing the mailbox never see the replies.

Context

Reported during the SURF Works pilot, which offers Nextcloud to Dutch research and
education institutions where functional/shared mailboxes are common.

Checklist

  • Code is properly formatted (php-cs-fixer clean)
  • Unit tests added and passing
  • Backwards compatible
  • Conventional commit message with DCO sign-off

The SASL XOAUTH2 identity for SMTP was hardcoded to the account's email
address, ignoring the configured SMTP user. For basic auth the SMTP user
is honoured, and provisioning can already derive a distinct one, so the
xoauth2 path was the only place where the setting had no effect.

This breaks sending from Microsoft 365 shared mailboxes. Exchange Online
accepts a shared mailbox as XOAUTH2 identity for IMAP but rejects it for
SMTP AUTH with "535 5.7.3 Authentication unsuccessful", because a shared
mailbox has no sign-in credentials of its own. The supported approach is
to authenticate as the delegate's own mailbox and rely on Send As for the
sender address, which is what other clients such as Thunderbird do.

Use the configured SMTP user as the XOAUTH2 identity and fall back to the
account's email address when it is empty, so existing accounts keep their
current behaviour. The IMAP client factory is left unchanged on purpose:
there the shared mailbox address is the correct identity.

Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
@welcome

welcome Bot commented Sep 10, 2026

Copy link
Copy Markdown

Thanks for opening your first pull request in this repository! ✌️

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.

SMTP XOAUTH2 authentication ignores the configured SMTP user

1 participant