toeverything/AFFiNE · error · EmailServiceNotConfigured

email_service_not_configured

email_service_not_configured

Error message

Email service is not configured.

What it means

EmailServiceNotConfigured (code=email_service_not_configured) thrown by Mailer.send when this.sender.configured is false and the caller did not pass suppressError. The mailer refuses to enqueue/attempt delivery because no SMTP/transactional provider is set up. When suppressError=true the call returns false instead.

Source

Thrown at packages/backend/server/src/core/mail/mailer.ts:96

      recipientUserId: metadata.recipientUserId,
      actorUserId: metadata.actorUserId,
      workspaceId: metadata.workspaceId,
      notificationId: metadata.notificationId,
      abuseSubjectKey: metadata.abuseSubjectKey,
      payload: serializePayload(command),
      expiresAt: metadata.expiresAt,
      maxAttempts: 0,
      lastErrorCode: options.reason,
    });
    return false;
  }

  async send(command: MailCommand, suppressError = false) {
    if (!this.sender.configured) {
      if (suppressError) {
        return false;
      }
      throw new EmailServiceNotConfigured();
    }

    let reservationId: string | undefined;
    let deliveryId: string | undefined;
    try {
      const metadata = command.metadata ?? {};
      const deduped = metadata.dedupeKey
        ? await this.models.mailDelivery.findByDedupeKey(metadata.dedupeKey)
        : null;
      if (deduped) {
        return !['failed', 'canceled', 'skipped'].includes(deduped.status);
      }

      const decision = await this.runtime.assertMailDeliveryQuotaV1({
        mailName: command.name as MailName,
        recipient: {
          email: command.to,
          domain: recipientDomain(command.to),

View on GitHub (pinned to 26c515e050)

Solutions

  1. Configure SMTP (or the configured transactional provider) in env and restart the server; verify sender.configured is true via the admin mail status endpoint.
  2. For non-critical mail paths, pass suppressError=true so the absence of mail degrades gracefully instead of failing the workflow.
  3. Gate mail-dependent features (invitations, verification) behind a 'mail configured' check in the UI.
  4. Add a startup warning/log when mail-dependent features are enabled but sender.configured is false.

Example fix

// before
async send(command: MailCommand, suppressError = false) {
  if (!this.sender.configured) {
    if (suppressError) return false;
    throw new EmailServiceNotConfigured();
  }
  ...
}

// caller side — pass suppressError for non-critical mail
await this.mailer.send(inviteMail, /* suppressError */ true);
Defensive patterns

Strategy: validation

Validate before calling

async function assertMailConfigured(mailer) {
  if (!mailer.sender.configured) {
    throw new UserError('Email service is not configured');
  }
}

Type guard

function isEmailNotConfigured(e: unknown): boolean {
  return e instanceof Error && (e as any).code === 'email_service_not_configured';
}

Try / catch

// For non-critical mail, suppress; for critical, surface a clear message
try {
  await mailer.send(mail);
} catch (e) {
  if (isEmailNotConfigured(e)) {
    return res.status(503).send('Email not configured on this server');
  }
  throw e;
}
// OR: await mailer.send(mail, /* suppressError */ true);

Prevention

When it happens

Trigger: Any mail-sending path (verification, invitation, notification, test mail) invoked on a deployment where the mail sender has no valid configuration. The check is on sender.configured, which is false until SMTP host/credentials (or the transactional provider) are provided.

Common situations: Fresh self-hosted install without SMTP env vars set. Mail config removed/rotated and not reapplied. A feature that sends mail enabled while mail is unconfigured (invites on, SMTP off).

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/4f01902a50ae3137. Report an issue: GitHub.