immich-app/immich · error · BadRequestException

Failed to verify SMTP configuration

Error message

Failed to verify SMTP configuration

What it means

Thrown by NotificationService.sendTestEmail when emailRepository.verifySmtp(dto.transport) rejects. It is a BadRequestException with the original SMTP error attached via { cause }, so NestJS responds with HTTP 400 and the cause is preserved in logs. The SMTP verification opens a real connection to the configured transport before any email is rendered or sent.

Source

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

    await this.jobRepository.queue({ name: JobName.NotifyAlbumInvite, data: { id, recipientId: userId, senderName } });
  }

  @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 199723261c)

Solutions

  1. Check the `cause` field on the exception: it carries the upstream nodemailer error (EAUTH, ECONNECTION, EENVELOPE, etc.).
  2. Verify host, port, and security setting (e.g. 465 with 'tls', 587 with 'starttls') against the provider's docs.
  3. Confirm outbound network access and DNS resolution from the immich-server container.
  4. Use provider-specific credentials (e.g. a Gmail App Password, not the account password).
  5. Test the same transport with swaks or nodemailer directly from the host to isolate immich vs SMTP server issues.

Example fix

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

// after (surface the upstream code so callers can branch)
try {
  await this.emailRepository.verifySmtp(dto.transport);
} catch (error) {
  throw new BadRequestException(`Failed to verify SMTP configuration: ${error.code ?? error.message}`, { cause: error });
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: open a throwaway SMTP connection before persisting the config.
try {
  await emailRepository.verifySmtp(dto.transport);
} catch (e) {
  // report the upstream code (EAUTH, ECONNECTION, ...) to the user
  return { ok: false, code: e.code, message: e.message };
}

Type guard

import { isIPv4 } from 'node:net';

const isLikelyValidTransport = (t: SmtpTransport): boolean =>
  !!t.host && (typeof t.port === 'number' && t.port > 0 && t.port < 65536);

if (!isLikelyValidTransport(dto.transport)) { /* reject early */ }

Try / catch

try {
  await notificationService.sendTestEmail(id, dto);
} catch (e) {
  if (e instanceof BadRequestException && /SMTP/.test(e.message)) {
    const upstream = (e as any).cause; // nodemailer error
    // surface upstream.code to the operator
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /system/preferences/notifications/test with a SystemConfigSmtpDto whose transport.host/port/username/password/security are wrong, unreachable, or whose credentials are rejected by the SMTP server.

Common situations: Typo in host/port, firewall blocking outbound SMTP (port 25/465/587), wrong auth credentials, STARTTLS vs SSL mismatch, self-signed cert rejected, Gmail requiring an app password, or the SMTP host not resolvable in the container's DNS.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/7e22376730343de8. Report an issue: GitHub.