When a user logs in, EmailTfaVerifyLoginForm::buildForm() calls $this->sendEmail() at line 177 during GET rendering of the verify form. sendEmail() then calls MailManagerInterface::mail(), which renders the mail body inside the active render context of the parent page.

With several common mail transports, including the contrib SMTP module pointed at an external relay, the first mail is accepted by the plugin (so $message['result'] is TRUE and the "Email sent successfully" status is shown) but the transport handoff at kernel.terminate does not fire. The message is effectively swallowed.

When the user clicks "Resend", sendEmailSubmitForm() runs as a clean POST submit handler with no active render context. That single request flushes both the stuck first message and the new resend at the transport level, so the recipient sees two codes arriving back to back several minutes after the first login attempt.

Steps to reproduce:
1. Enable email_tfa 3.0.0 with globally_enabled tracks.
2. Configure a non-default mail transport (contrib SMTP plus an external relay like Brevo, SendGrid, or Postmark).
3. Log in with a covered account.
4. Observe that Drupal reports "Email sent successfully" but nothing appears in the SMTP relay dashboard.
5. Click "Resend".
6. Two messages arrive back to back.

Proposed fix: move the initial mail dispatch out of EmailTfaVerifyLoginForm::buildForm() and into EmailTfaLoginForm::submitForm(), where it runs after password authentication in a POST context. The existing tempstore '_email_tfa_send_mail' flag guard in the verify form's buildForm then correctly short-circuits the duplicate send.

Tested against 3.0.0 on Drupal 11.3.7, PHP 8.3, contrib SMTP 1.x with Brevo as the external SMTP relay. After the patch, the first email is dispatched immediately on login submit. Any remaining delivery delay is attributable to the SMTP relay itself, not Drupal.

Patch attached.

Comments

kruser created an issue.