Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion apps/docs/content/docs/en/self-hosting/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,48 @@ import { Callout } from 'fumadocs-ui/components/callout'
| `API_ENCRYPTION_KEY` | Encrypts stored API keys (32 hex chars): `openssl rand -hex 32` |
| `COPILOT_API_KEY` | API key for copilot features |
| `ADMIN_API_KEY` | Admin API key for GitOps operations |
| `RESEND_API_KEY` | Email service for notifications |
| `ALLOWED_LOGIN_DOMAINS` | Restrict signups to domains (comma-separated) |
| `ALLOWED_LOGIN_EMAILS` | Restrict signups to specific emails (comma-separated) |
| `DISABLE_REGISTRATION` | Set to `true` to disable new user signups |

## Email Providers

Configure one provider — the mailer auto-detects in priority order: **Resend → AWS SES → SMTP → Azure Communication Services**. If none are configured, emails are logged to the console instead.

| Variable | Description |
|----------|-------------|
| `FROM_EMAIL_ADDRESS` | Sender address (e.g. `Sim <noreply@example.com>`). Falls back to `noreply@EMAIL_DOMAIN`. |
| `EMAIL_DOMAIN` | Default domain when `FROM_EMAIL_ADDRESS` is unset |
| `EMAIL_VERIFICATION_ENABLED` | Set to `true` to require email verification on signup |

**Resend**

| Variable | Description |
|----------|-------------|
| `RESEND_API_KEY` | API key from [resend.com](https://resend.com) |

**AWS SES**

| Variable | Description |
|----------|-------------|
| `AWS_SES_REGION` | AWS region for SES (e.g. `us-east-1`). Credentials are resolved through the standard AWS SDK provider chain (env vars, IRSA, ECS/EC2 instance role, SSO). |

**SMTP** (works with MailHog, Postfix, SendGrid SMTP, etc.)

| Variable | Description |
|----------|-------------|
| `SMTP_HOST` | SMTP server hostname |
| `SMTP_PORT` | `465` for implicit TLS, `587` for STARTTLS, `25` for plain |
| `SMTP_USER` | Optional — omit for unauthenticated relays |
| `SMTP_PASS` | Optional — omit for unauthenticated relays |
| `SMTP_SECURE` | Set to `true` to force TLS on connect; auto-true on port 465 |

**Azure Communication Services**

| Variable | Description |
|----------|-------------|
| `AZURE_ACS_CONNECTION_STRING` | Azure Communication Services connection string |

## Example .env

```bash
Expand Down
26 changes: 24 additions & 2 deletions apps/sim/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,30 @@ INTERNAL_API_SECRET=your_internal_api_secret # Use `openssl rand -hex 32` to gen
API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to generate, used to encrypt api keys

# Email Provider (Optional)
# RESEND_API_KEY= # Uncomment and add your key from https://resend.com to send actual emails
# If left commented out, emails will be logged to console instead
# Configure ONE provider — the mailer auto-detects in priority order:
# Resend → AWS SES → SMTP → Azure Communication Services. If none are
# configured, emails are logged to console instead.
#
# Resend
# RESEND_API_KEY= # API key from https://resend.com
#
# AWS SES (credentials resolved via the standard AWS provider chain:
# env vars, shared config, ECS/EKS task role, EC2 instance profile, SSO)
# AWS_SES_REGION=us-east-1
#
# SMTP (works with MailHog locally: host=localhost port=1025, no auth)
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587 # 465 = implicit TLS, 587 = STARTTLS, 25 = plain
# SMTP_USER= # Optional — omit for unauthenticated relays
# SMTP_PASS= # Optional — omit for unauthenticated relays
# SMTP_SECURE= # Set "true" to force TLS on connect; auto-true on port 465
#
# Azure Communication Services
# AZURE_ACS_CONNECTION_STRING=
#
# Shared sender configuration
# FROM_EMAIL_ADDRESS="Sim <noreply@example.com>"
# EMAIL_DOMAIN=example.com # Fallback when FROM_EMAIL_ADDRESS is unset

# Local AI Models (Optional)
# OLLAMA_URL=http://localhost:11434 # URL for local Ollama server - uncomment if using local models
Expand Down
19 changes: 19 additions & 0 deletions apps/sim/lib/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ export const env = createEnv({
PERSONAL_EMAIL_FROM: z.string().min(1).optional(), // From address for personalized emails
EMAIL_DOMAIN: z.string().min(1).optional(), // Domain for sending emails (fallback when FROM_EMAIL_ADDRESS not set)
AZURE_ACS_CONNECTION_STRING: z.string().optional(), // Azure Communication Services connection string
AWS_SES_REGION: z.string().min(1).optional(), // AWS region for SES (credentials resolved via default SDK provider chain)
SMTP_HOST: z.string().min(1).optional(), // SMTP server hostname
SMTP_PORT: z.coerce.number().int().min(1).max(65535).optional(),
SMTP_USER: z.string().min(1).optional(), // SMTP username
SMTP_PASS: z.string().min(1).optional(), // SMTP password
SMTP_SECURE: z.boolean().optional(), // Force TLS on connect (defaults to true on port 465); read via envBoolean to handle string values from process.env

// SMS & Messaging
TWILIO_ACCOUNT_SID: z.string().min(1).optional(), // Twilio Account SID for SMS sending
Expand Down Expand Up @@ -555,3 +561,16 @@ export function envNumber(
const parsed = Number(value)
return Number.isFinite(parsed) && parsed >= min ? parsed : fallback
}

/**
* Coerce an env-derived value to a boolean. Returns `undefined` when unset
* so callers can apply context-aware defaults. Required because
* `Boolean("false") === true`, so `z.coerce.boolean()` would silently flip
* the meaning of `MY_FLAG=false`.
*/
export function envBoolean(value: boolean | string | undefined | null): boolean | undefined {
if (typeof value === 'boolean') return value
if (value === undefined || value === null || value === '') return undefined
const normalized = String(value).trim().toLowerCase()
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on'
}
14 changes: 14 additions & 0 deletions apps/sim/lib/messaging/email/mailer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,5 +251,19 @@ describe('mailer', () => {

expect(isUnsubscribed).not.toHaveBeenCalled()
})

it('should degrade isUnsubscribed rejections to per-entry failures', async () => {
;(isUnsubscribed as Mock).mockRejectedValue(new Error('Database connection failed'))

const result = await sendBatchEmails({
emails: [
{ ...testEmailOptions, to: 'user1@example.com', emailType: 'marketing' as EmailType },
{ ...testEmailOptions, to: 'user2@example.com', emailType: 'marketing' as EmailType },
],
})

expect(result.results).toHaveLength(2)
expect(result.results.every((r) => r.success === false)).toBe(true)
})
})
})
Loading
Loading