feat(email): add SMTP support for self-hosted platform emails#3696
feat(email): add SMTP support for self-hosted platform emails#3696xid-ryan wants to merge 10 commits intosimstudioai:mainfrom
Conversation
- ANTHROPIC_BASE_URL: allows custom Anthropic API proxy endpoint - USE_SERVER_KEYS / NEXT_PUBLIC_USE_SERVER_KEYS: enables server-configured rotating API keys for self-hosted deployments, removing the need for users to enter API keys in the UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add SMTP support to the shared mailer so self-hosted deployments can send auth and notification emails without relying on Resend or Azure. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Expose SMTP configuration through the app env surface so self-hosted operators can wire a custom mail server without patching code. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Document the new SMTP settings and provider precedence so operators know how platform email delivery behaves in self-hosted environments. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add SMTP-related values to the Helm chart so deployments can configure custom mail servers through standard release configuration. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Route SMTP credentials through app secrets so Helm deployments can consume them without leaking usernames or passwords into inline environment values. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
|
@wickedev is attempting to deploy a commit to the Sim Team on Vercel. A member of the Team first needs to authorize it. |
PR SummaryMedium Risk Overview Extends self-hosted surfaces to support SMTP credentials end-to-end (docs, Also introduces a self-hosted Written by Cursor Bugbot for commit 965ae39. This will update automatically on new commits. Configure here. |
Greptile SummaryThis PR adds SMTP as a third platform email backend alongside the existing Resend and Azure Communication Services providers, and introduces server-managed API keys ( Key findings:
Confidence Score: 3/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[sendEmail called] --> B{emailProviders empty?}
B -- Yes --> C[Return success, log-only mode]
B -- No --> D[Try Resend]
D -- Success --> E[Return via Resend]
D -- Fail --> F[Try Azure ACS]
F -- Success --> G[Return via Azure ACS]
F -- Fail --> H[Try SMTP]
H -- Success --> I[Return via SMTP]
H -- Fail --> J[Return failure, all providers failed]
subgraph smtpInit[SMTP Initialization]
K[Read SMTP env vars] --> L{SMTP_HOST set?}
L -- No --> M[Return null, no SMTP]
L -- Yes --> N{SMTP_PORT valid?}
N -- No --> O[Warn and return null]
N -- Yes --> P[Build SmtpConfig]
P --> Q[createSmtpTransporter via nodemailer]
end
Q --> B
|
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
AWS account ID and ECR registry URL hardcoded
The ECR registry URL 310455165573.dkr.ecr.us-west-2.amazonaws.com exposes the AWS account ID directly in the repository's source history. It is safer to store this in a GitHub Actions secret (e.g., ${{ secrets.ECR_REGISTRY }}) so the account ID is not committed to version control.
| steps: | |
| - uses: actions/checkout@v4 | |
| ECR_REGISTRY: ${{ secrets.ECR_REGISTRY }} |
| SMTP_HOST: 'smtp.test.sim.ai', | ||
| SMTP_PORT: '587', | ||
| SMTP_SECURE: 'TLS', | ||
| SMTP_USERNAME: 'smtp-user', | ||
| SMTP_PASSWORD: 'smtp-password', |
There was a problem hiding this comment.
Adding SMTP to the global default mock may break existing email tests
The defaultMockEnv object is used by every test across the monorepo that calls createEnvMock(). The existing default already includes RESEND_API_KEY, so adding a fully-configured SMTP block means the mailer module will now boot with two providers (Resend + SMTP) in any test that uses this default mock. This triggers warnOnMultipleProviders() at module-load time, which could:
- Cause tests that assert on logger output to see an unexpected
warncall about "Multiple email providers configured". - Change the observable
emailProviderscount from 1 to 2, silently altering test behaviour for any test that relies onhasEmailService()or the fallback path.
Consider keeping the global default to a single minimal provider (e.g., just RESEND_API_KEY), and adding full SMTP credentials only in test suites that specifically exercise SMTP behaviour.
| SMTP_PORT: "587" # SMTP port (587 for STARTTLS, 465 for SSL) | ||
| SMTP_SECURE: "TLS" # SMTP security mode: TLS, SSL, or None |
There was a problem hiding this comment.
Spurious warning logged for every deployment without SMTP
The default values SMTP_PORT: "587" and SMTP_SECURE: "TLS" are always rendered into the pod's environment via the range loop in deployment-app.yaml. When SMTP_HOST is left blank (the default), getSmtpConfig() in mailer.ts receives an empty host but a non-empty portValue of "587". This means the silent early-return guard:
if (!host && !portValue && !username && !password) {
return null // silent skip
}…is not triggered, because portValue is truthy. Execution then falls through to the next guard which emits logger.warn('SMTP configuration ignored because host or port is missing') — this warning fires on every pod start for deployments that don't configure SMTP at all.
Note: skipValidation: true is set in env.ts, so empty-string values are NOT coerced to undefined by Zod; SMTP_HOST="" remains a falsy empty string.
Suggested fix (in getSmtpConfig()): check for a missing host first and return null silently, since the presence of a host is the minimal signal that the operator intended to configure SMTP:
// If host is absent, SMTP is simply not configured — return silently.
if (!host) {
return null
}
if (!portValue) {
logger.warn('SMTP configuration ignored because port is missing')
return null
}There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| EMAIL_DOMAIN: "" # Domain for sending emails (fallback when FROM_EMAIL_ADDRESS not set) | ||
| SMTP_HOST: "" # SMTP hostname for app-level outgoing email (alternative to Resend/Azure) | ||
| SMTP_PORT: "587" # SMTP port (587 for STARTTLS, 465 for SSL) | ||
| SMTP_SECURE: "TLS" # SMTP security mode: TLS, SSL, or None |
There was a problem hiding this comment.
Helm defaults cause spurious SMTP warning on startup
Medium Severity
The default Helm values set SMTP_PORT: "587" and SMTP_SECURE: "TLS" while leaving SMTP_HOST: "". In getSmtpConfig(), the early-return check !host && !portValue && !username && !password evaluates to false because portValue is truthy ("587"). This causes the function to fall through to the next check, which logs a warning: "SMTP configuration ignored because host or port is missing". Every default Helm deployment that hasn't configured SMTP will see this spurious warning on every startup.
Additional Locations (1)
| env: | ||
| AWS_REGION: us-west-2 | ||
| ECR_REGISTRY: 310455165573.dkr.ecr.us-west-2.amazonaws.com | ||
| ECR_REPOSITORY: sim-app |
There was a problem hiding this comment.
ECR workflow exposes AWS account ID in public repo
Low Severity
The new build-ecr.yml workflow hardcodes the AWS account ID (310455165573) and ECR repository details directly in the file. This is unrelated to the SMTP feature described in the PR and exposes internal infrastructure details in a public repository. It also runs on every push to main, building and pushing a :latest tag without a commit SHA, which means image provenance is lost.


Summary
Verification