Skip to content

Email Notifications

Sia edited this page May 31, 2026 · 3 revisions

Email Notifications

SMTP-based operational alerts. Default is disabled (email.enabled: false). View / test at /settings/email.

Triggers

Event Subject Body
Build SUCCESS Build SUCCESS — <projectId>/<buildId> Project + build id + link
Build FAILED Build FAILED — <projectId>/<buildId> Same + error message (first 2 000 chars)
Claude usage warning Claude usage warning: N% remaining Quota + reset time
Disk usage warning Disk usage warning: N% used Used % + free GB

Build CANCELED is not notified — the user just clicked cancel, they don't need a mail telling them so.

Claude usage thresholds

ClaudeUsageMonitor polls claude /status every 5 min (configurable in server.yml under claude.usage.pollIntervalMinutes). The notifier fires on transition past two thresholds:

  • claude.usage.warnThresholdPercent (default 80)
  • claude.usage.criticalThresholdPercent (default 95)

Once active for a level, no further mails until the value drops back below — then a fresh crossing fires again. 10-minute cooldown prevents flapping.

Disk usage threshold

DiskMonitor polls Files.getFileStore(workspace.root) every 10 min. Fires on transition past email.diskUsageWarnPercent (default 85). Same "transition only + cooldown" pattern. 30-minute cooldown.

The same threshold is reused by the webhook notifier — see Webhook Notifications and Disk Monitor.

Configuration

Two paths, env recommended (keeps the password out of any tracked file):

Option A — .env (recommended)

VIBECODER_SMTP_ENABLED=true
VIBECODER_SMTP_HOST=smtp.gmail.com
VIBECODER_SMTP_PORT=587
VIBECODER_SMTP_TLS=true
VIBECODER_SMTP_USER=alerts@example.com
VIBECODER_SMTP_PASSWORD=app-specific-password   # Gmail: app password, not the login one
VIBECODER_SMTP_FROM=vibe-coder <alerts@example.com>
VIBECODER_SMTP_TO=ops@example.com,me@personal.com

Option B — server.yml

email:
  enabled: true
  host: smtp.gmail.com
  port: 587
  tls: true
  user: alerts@example.com
  passwordFile: /run/secrets/smtp_password   # Docker secret — preferred over inline password
  from: "vibe-coder <alerts@example.com>"
  to: ops@example.com,me@personal.com
  claudeUsageWarnPercent: 20
  diskUsageWarnPercent: 85

passwordFile is read at boot; the file isn't watched, so a password rotation requires a restart.

Provider quick reference

Provider Host Port Notes
Gmail smtp.gmail.com 587 App password required (the normal account password no longer works)
Outlook / Office 365 smtp.office365.com 587 App password if 2FA enabled
AWS SES email-smtp.<region>.amazonaws.com 587 Get SMTP credentials via SES console
Mailgun / SendGrid provider host 587 API key acts as the SMTP password
Self-hosted Postfix LAN host 25 (no TLS) or 587 Set tls: false for plain 25

Verification

/settings/email has a single "Test mail send" button (only enabled when email.enabled = true). It calls EmailNotifier.sendNow synchronously and shows the success/failure result inline. Use this after any configuration change rather than waiting for a real build to fail.

Implementation notes

  • Engine: Jakarta Mail 2.1.3 API + Angus Mail 2.0.3 implementation.
  • Async by default — EmailNotifier.send(subject, body) returns immediately and the actual SMTP socket is opened on a background dispatcher. Build routes never block on the mail server being slow.
  • sendNow is the synchronous version used by the test button.
  • Failures (auth, DNS, timeout) are logged to stderr with the exception message. Audit log does not record mail failures (out of scope).

Operator hardening checklist

  • VIBECODER_SMTP_PASSWORD lives only in .env (chmod 0600) or a Docker secret — not in server.yml.
  • tls: true for any non-localhost provider.
  • from matches the authenticated SMTP user (DMARC / SPF).
  • to includes at least one email you actually check.
  • Test mail succeeds after restart.

Disabling

Set email.enabled: false (or unset VIBECODER_SMTP_ENABLED). All EmailNotifier calls become no-ops. No restart required for disable, since the config is read fresh per send — but env vars only take effect on restart.

Clone this wiki locally