How to Configure Email Notifications with SMTP Settings in TREK

TREK sends email notifications for password resets, trip invites, and booking changes through an SMTP server configured either via environment variables or the Admin UI, with settings merged by the getSmtpConfig() function in server/src/services/notifications.ts.

The TREK open-source travel management platform uses nodemailer to dispatch transactional emails. You can configure the SMTP connection using two methods: environment variables for infrastructure-as-code deployments, or the database-backed Admin UI for runtime configuration. Both methods ultimately feed into the same notification service that handles encryption, transport creation, and error logging.

Configuration Sources and Priority

TREK reads SMTP settings from two locations and merges them with a clear precedence order:

  1. Environment variables (highest priority): SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM, SMTP_SKIP_TLS_VERIFY
  2. Database settings (app_settings table): smtp_host, smtp_port, smtp_user, smtp_pass, smtp_from, smtp_skip_tls_verify

The getSmtpConfig() function in server/src/services/notifications.ts implements this fallback logic:

function getSmtpConfig(): SmtpConfig | null {
  const host = process.env.SMTP_HOST || getAppSetting('smtp_host');
  const port = process.env.SMTP_PORT || getAppSetting('smtp_port');
  const user = process.env.SMTP_USER || getAppSetting('smtp_user');
  const pass = process.env.SMTP_PASS || decrypt_api_key(getAppSetting('smtp_pass')) || '';
  const from = process.env.SMTP_FROM || getAppSetting('smtp_from');
  if (!host || !port || !from) return null;
  return { host, port: parseInt(port, 10), user: user || '', pass: pass || '', from, secure: parseInt(port, 10) === 465 };
}

Important: The smtp_pass value stored in the database is encrypted using the ENCRYPTION_KEY. If you rotate or lose this key, the stored password becomes unreadable and must be re-entered.

Setting SMTP Variables

Via Docker and Environment Variables

For containerized deployments, export the SMTP variables in your docker-compose.yml or .env file. Port 465 enables implicit TLS, while 587 uses STARTTLS:

services:
  trek:
    image: mauriceboe/trek:latest
    environment:
      - SMTP_HOST=smtp.example.com
      - SMTP_PORT=587
      - SMTP_USER=trekmailer
      - SMTP_PASS=super-secret
      - SMTP_FROM=TREK <noreply@example.com>
      - SMTP_SKIP_TLS_VERIFY=false
      - ENCRYPTION_KEY=$(openssl rand -hex 32)

Place the .env file adjacent to your compose file, or inject the variables directly via your orchestration platform.

Via the Admin Panel

If you prefer runtime configuration without restarting containers:

  1. Log in as an administrator.
  2. Navigate to Admin → Notifications.
  3. In the Email (SMTP) panel, fill in Host, Port, User, Password, From address, and optionally Skip TLS verification.
  4. Click Save to persist encrypted values to the app_settings table.

The UI exposes the same fields as the environment variables, offering flexibility for non-technical administrators.

How TREK Uses SMTP Configuration

Once configured, TREK initializes a nodemailer transport on demand for two primary notification types:

  • Password resets: The sendPasswordResetEmail() function generates a one-time link. If no SMTP configuration is present, the link falls back to the server console.
  • Event notifications: The sendEmail() function handles trip invites, booking changes, and custom alerts. It assembles HTML via buildEmailHtml() and transmits messages through the transport created from getSmtpConfig().

Both functions log outcomes using logInfo and logError, enabling visibility into delivery success or transport failures via container logs.

Testing Your SMTP Configuration

TREK provides a built-in test helper accessible programmatically or through the UI. The testSmtp() function in server/src/services/notifications.ts validates connectivity by sending a probe message:

export async function testSmtp(to: string): Promise<{ success: boolean; error?: string }> {
  if (!getSmtpConfig()) return { success: false, error: 'SMTP not configured' };
  try {
    const config = getSmtpConfig()!;
    const skipTls = process.env.SMTP_SKIP_TLS_VERIFY === 'true' || getAppSetting('smtp_skip_tls_verify') === 'true';
    const transporter = nodemailer.createTransport({
      host: config.host,
      port: config.port,
      secure: config.secure,
      auth: config.user ? { user: config.user, pass: config.pass } : undefined,
      ...(skipTls ? { tls: { rejectUnauthorized: false } } : {}),
    });
    await transporter.sendMail({
      from: config.from,
      to,
      subject: 'TREK — Test Notification',
      text: 'This is a test email from TREK. If you received this, your SMTP configuration is working correctly.',
    });
    return { success: true };
  } catch (err) {
    return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
  }
}

Manual API test:

curl -X POST http://localhost:3001/api/admin/notifications/test-smtp \
     -H "Authorization: Bearer <admin-jwt>" \
     -d '{"email":"you@example.com"}'

A successful response returns {"success": true} and delivers the test message to your inbox.

Troubleshooting Common SMTP Issues

Symptom Likely Cause Fix
No email arrives; logs show "SMTP not configured" Missing SMTP_HOST, SMTP_PORT, or SMTP_FROM Verify all three required variables are set in environment or Admin UI
Connection refused or timeout Incorrect host/port or firewall blocking outbound SMTP Test connectivity with nc -zv $SMTP_HOST $SMTP_PORT from inside the container
TLS handshake error Using port 587 with secure: true or self-signed certificates Use SMTP_SKIP_TLS_VERIFY=true only for internal self-signed servers, or switch to port 465 for implicit TLS
Password reset link appears in console No SMTP configured or environment variables not loaded Set SMTP variables and restart the container if using env vars
SMTP password suddenly invalid after restart ENCRYPTION_KEY changed or lost Maintain a persistent ENCRYPTION_KEY in a volume or secret store; otherwise re-enter the password in the Admin UI

Summary

  • TREK supports dual configuration paths: Environment variables take precedence over database settings stored in the app_settings table.
  • Encryption is mandatory: The ENCRYPTION_KEY protects sensitive values like SMTP_PASS; losing it requires reconfiguration.
  • Testing is built-in: Use the testSmtp() function or Admin UI button to verify connectivity before production use.
  • Logs provide visibility: Check server logs for logInfo and logError output from sendEmail() and sendPasswordResetEmail() to diagnose delivery issues.
  • Port selection matters: Use 465 for implicit TLS or 587 for STARTTLS, matching your provider's requirements.

Frequently Asked Questions

Can I use TREK without configuring SMTP?

Yes. If SMTP_HOST, SMTP_PORT, and SMTP_FROM are unset and the database fields are empty, TREK operates without email functionality. Password reset links will appear in the server console instead of being emailed, and trip notifications will not be sent.

Why does my SMTP password stop working after a container restart?

The password stored in the database is encrypted with the ENCRYPTION_KEY. If this key changes or is not persisted across restarts (e.g., using $(openssl rand -hex 32) without saving the output), TREK cannot decrypt the password. Store the key in a persistent volume or secret management system, or re-enter the password via the Admin UI.

How do I send custom emails from my TREK extension?

Import the sendEmail function from server/src/services/notifications.ts and invoke it with recipient, subject, body, user ID, and optional path:

import { sendEmail } from '@trek/server/services/notifications';

await sendEmail('user@example.com', 'Custom Alert', 'Message body', 42, '/dashboard');

This reuses the existing SMTP transport and logging infrastructure.

What is the difference between SMTP_SKIP_TLS_VERIFY and choosing port 465 versus 587?

SMTP_SKIP_TLS_VERIFY disables certificate validation for self-signed certificates and should only be used in development environments. Port 465 enables implicit TLS (secure connection from the start), while port 587 uses STARTTLS (upgrade to TLS after connection). Use 465 with secure: true or 587 with secure: false (STARTTLS) depending on your provider's requirements.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →