immich-app/immich · error · BadRequestException

Failed to verify SMTP configuration

Error message

Failed to verify SMTP configuration

What it means

sendTestEmail validates the SMTP transport by attempting a live verification (verifySmtp) before rendering and sending a test email. If the repository's SMTP check fails for any reason (bad host, port, credentials, TLS), the original error is wrapped in a BadRequestException with this message. It signals the stored/system email config cannot be used to send mail.

Solutions

  1. Verify SMTP host, port, username, and password in the notification settings; ensure the secure flag matches the port (465=secure, 587=starttls).
  2. Test connectivity from the server host (e.g. `nc -vz smtp.example.com 587`) to rule out firewall/DNS issues.
  3. If using Gmail or Office365, use an app password / SMTP AUTH with less-secure-app or OAuth settings.
  4. Inspect the `cause` of the BadRequestException in server logs for the underlying SMTP error detail.

Example fix

// before
await this.emailRepository.verifySmtp({ host: 'smtp.gmail.com', port: 587, secure: true });
// after
await this.emailRepository.verifySmtp({ host: 'smtp.gmail.com', port: 587, secure: false }); // 587 uses STARTTLS, not implicit TLS
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check config before calling
const ok = smtpHost && smtpPort > 0 && smtpPort < 65536 && (smtpUsername ? smtpPassword !== undefined : true);
if (!ok) throw new Error('Incomplete SMTP settings');

Try / catch

try {
  await api.notificationsApi.sendTestEmail({ transport });
} catch (e) {
  if (e.status === 400 && String(e.message).includes('Failed to verify SMTP configuration')) {
    // surface e.cause / check host, port, credentials, TLS
  } else throw e;
}

Prevention

When it happens

Trigger: Calling POST /api/notifications/test-email (sendTestEmail) with a TestEmailDto whose transport (smtp host/port/username/password/secure settings) fails the verifySmtp check — connection refused, auth failure, or TLS handshake error.

Common situations: Wrong SMTP host or port (465 vs 587 mismatch with secure flag), incorrect username/password, self-signed certificates rejected by TLS, firewall blocking outbound SMTP, Gmail requiring app passwords.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/7e22376730343de8. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/notification.service.ts:268

    this.websocketRepository.clientSend('on_notification', userId, mapNotification(item));
  }

  @OnEvent({ name: 'SessionDelete' })
  onSessionDelete({ sessionId }: ArgOf<'SessionDelete'>) {
    // after the response is sent
    setTimeout(() => this.websocketRepository.clientSend('on_session_delete', sessionId, sessionId), 500);
  }

  async sendTestEmail(id: string, dto: SystemConfigSmtpDto, tempTemplate?: string) {
    const user = await this.userRepository.get(id, { withDeleted: false });
    if (!user) {
      throw new Error('User not found');
    }

    try {
      await this.emailRepository.verifySmtp(dto.transport);
    } catch (error) {
      throw new BadRequestException('Failed to verify SMTP configuration', { cause: error });
    }

    const { server } = await this.getConfig({ withCache: false });
    const { html, text } = await this.emailRepository.renderEmail({
      template: EmailTemplate.TEST_EMAIL,
      data: {
        baseUrl: getExternalDomain(server),
        displayName: user.name,
      },
      customTemplate: tempTemplate!,
    });
    const { messageId } = await this.emailRepository.sendEmail({
      to: user.email,
      subject: 'Test email from Immich',
      html,
      text,
      from: dto.from,
      replyTo: dto.replyTo || dto.from,

View on GitHub (pinned to e55ac299a4)