Bug description
occ user:add --email … prints Welcome email sent to <address> even when the mail was
never sent, because Mailer::send() swallows the transport failure instead of throwing.
occ user:welcome has the same root cause and is worse: it prints nothing on success, so a
failed send is indistinguishable from a successful one — it just exits 0.
Both commands do have an error branch. Neither branch can ever be reached.
Steps to reproduce
- Configure an SMTP account whose credentials are invalid (or otherwise make the transport fail).
occ user:add --display-name "Test" --email test@example.org testuser
Expected behaviour
Unable to send the welcome email to test@example.org — the branch that already exists in
core/Command/User/Add.php.
Actual behaviour
Welcome email sent to test@example.org
and, in nextcloud.log:
Failed to authenticate on SMTP server with username "…" using the following
authenticators: "LOGIN", "PLAIN". … "535 Incorrect authentication data"
The account is created and the operator is told the invitation is on its way. Nothing in the
command output distinguishes this from a successful send.
Where the failure is lost
Three links, each individually reasonable, that together make the failure unreportable:
-
lib/private/Mail/Mailer.php — send() catches TransportExceptionInterface, logs it, and
returns the failed-recipient list rather than re-throwing:
try {
$mailer->send($message->getSymfonyEmail());
} catch (TransportExceptionInterface $e) {
$this->logger->error($logMessage, ['app' => 'core', 'exception' => $e]);
…
return $failedRecipients; // <- no throw
}
-
apps/settings/lib/Mailer/NewUserMailHelper.php — sendMail() is declared : void and
discards that return value:
public function sendMail(IUser $user, IEMailTemplate $emailTemplate): void {
…
$this->mailer->send($message); // <- return value dropped
}
-
core/Command/User/Add.php — waits for an exception that can no longer arrive:
try {
$this->mailHelper->sendMail($user, $this->mailHelper->generateTemplate($user, true));
$output->writeln('Welcome email sent to ' . $email);
} catch (\Exception $e) {
$output->writeln('Unable to send the welcome email to ' . $email);
}
-
core/Command/User/Welcome.php — same shape, and no success output to contradict:
try {
$this->newUserMailHelper->sendMail($user, $emailTemplate);
} catch (\Exception $e) {
$output->writeln('<error>Failed to send email: ' . $e->getMessage() . '</error>');
return 1;
}
return 0;
A script that checks the exit code of occ user:welcome is therefore told the mail was
sent, in every case where the transport fails.
Why this matters beyond the command output
A silent failure would send an administrator looking. A message asserting success stops the
search: the account is created, the invitation is believed sent, and the person waits for a mail
that will never arrive. On an instance where mail is the only channel that lets a user set their
own password, this is the difference between "mail is down" and "mail is down and nobody knows".
The same swallowed return value means NewUserMailHelper::sendMail() cannot report failure to
any of its callers, not only this command.
Suggested fix
Either of these would restore the existing error branch; the first is the smaller change:
- have
NewUserMailHelper::sendMail() return the failed-recipient list from
IMailer::send() (or throw when it is non-empty), and have Add.php and Welcome.php
check it; or
- have
Mailer::send() re-throw after logging, since every caller that wants to tolerate a
failure can already catch it.
Server configuration
- Nextcloud: 34.0.3 (
nextcloud:34-apache)
- Reproduced with an SMTP account returning
535 Incorrect authentication data
Bug description
occ user:add --email …printsWelcome email sent to <address>even when the mail wasnever sent, because
Mailer::send()swallows the transport failure instead of throwing.occ user:welcomehas the same root cause and is worse: it prints nothing on success, so afailed send is indistinguishable from a successful one — it just exits 0.
Both commands do have an error branch. Neither branch can ever be reached.
Steps to reproduce
occ user:add --display-name "Test" --email test@example.org testuserExpected behaviour
Unable to send the welcome email to test@example.org— the branch that already exists incore/Command/User/Add.php.Actual behaviour
and, in
nextcloud.log:The account is created and the operator is told the invitation is on its way. Nothing in the
command output distinguishes this from a successful send.
Where the failure is lost
Three links, each individually reasonable, that together make the failure unreportable:
lib/private/Mail/Mailer.php—send()catchesTransportExceptionInterface, logs it, andreturns the failed-recipient list rather than re-throwing:
apps/settings/lib/Mailer/NewUserMailHelper.php—sendMail()is declared: voidanddiscards that return value:
core/Command/User/Add.php— waits for an exception that can no longer arrive:core/Command/User/Welcome.php— same shape, and no success output to contradict:A script that checks the exit code of
occ user:welcomeis therefore told the mail wassent, in every case where the transport fails.
Why this matters beyond the command output
A silent failure would send an administrator looking. A message asserting success stops the
search: the account is created, the invitation is believed sent, and the person waits for a mail
that will never arrive. On an instance where mail is the only channel that lets a user set their
own password, this is the difference between "mail is down" and "mail is down and nobody knows".
The same swallowed return value means
NewUserMailHelper::sendMail()cannot report failure toany of its callers, not only this command.
Suggested fix
Either of these would restore the existing error branch; the first is the smaller change:
NewUserMailHelper::sendMail()return the failed-recipient list fromIMailer::send()(or throw when it is non-empty), and haveAdd.phpandWelcome.phpcheck it; or
Mailer::send()re-throw after logging, since every caller that wants to tolerate afailure can already catch it.
Server configuration
nextcloud:34-apache)535 Incorrect authentication data