Enable self-hosted deployment with BunMail, SMTP, and Docker fixes.#3492
Enable self-hosted deployment with BunMail, SMTP, and Docker fixes.#3492raedbrahem wants to merge 1 commit into
Conversation
Add BunMail REST and SMTP email transports with direct send fallback when Trigger.dev is unset, wire the API service into docker-compose, and fix Prisma TLS for local Postgres hostnames.
|
@raedbrahem is attempting to deploy a commit to the Comp AI Team on Vercel. A member of the Team first needs to authorize it. |
|
|
There was a problem hiding this comment.
7 issues found across 12 files
Confidence score: 2/5
- In
docker-compose.yml, the service env overrides point at Postgres host/credentials that do not exist in this repo’s DB setup, so self-hosted services are likely to fail at startup or crash-loop when they try to connect to the database — align these env values with the actual compose Postgres service definitions. - In
apps/api/src/email/email-transport.ts, the SMTP path dropsscheduledAt, which can send scheduled emails immediately and create user-facing timing/compliance issues — defer SMTP sends untilscheduledAtor explicitly reject scheduled SMTP requests. - In
apps/api/src/email/email-transport.ts(sendHtmlEmail), the BunMail branch omitsheadersandattachments, so unsubscribe headers and file attachments can be silently lost in production emails — forward both fields in the BunMail payload and add coverage for these cases. docker-compose.ymlnow requires an externalcomp_network, andpackages/db/src/ssl-config.tslacks a regression test for the new Docker hostname, making local/self-hosted bring-up more fragile and harder to debug when networking or TLS behavior shifts — document/provision the network requirement and add the hostname SSL test case.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/db/src/ssl-config.ts">
<violation number="1" location="packages/db/src/ssl-config.ts:6">
P3: The new Docker hostname has no regression test, so an allowlist typo or later URL-parsing change can make self-hosted containers attempt TLS and fail to connect. Add a `resolveSslConfig('postgresql://u:p@comp-postgres:5432/x', {})` case asserting `undefined`.</violation>
</file>
<file name="docker-compose.yml">
<violation number="1" location="docker-compose.yml:40">
P1: The self-hosted services cannot connect to the repository's Postgres container: these overrides replace each service's env-file URL with a host and credentials that do not exist in the provided database compose setup. Use the actual database service/credentials or parameterize `DATABASE_URL` so deployments can supply their own connection string.</violation>
<violation number="2" location="docker-compose.yml:104">
P2: The `networks.default` overrides the project's default network to `comp_network` with `external: true`, which means this Docker network must already exist before `docker-compose up` will succeed. Running the compose file for the first time without a prior `docker network create comp_network` will fail with an opaque network-not-found error. Consider either dropping `external: true` so Docker Compose creates the network automatically, or adding a `docker network create` step to the project's setup documentation.</violation>
</file>
<file name="apps/api/src/email/email-transport.ts">
<violation number="1" location="apps/api/src/email/email-transport.ts:212">
P1: When BunMail is configured, the `sendHtmlEmail` function silently drops both `headers` and `attachments` from the outgoing email payload. This means the RFC 8058 one-click unsubscribe headers (`List-Unsubscribe` / `List-Unsubscribe-Post`) that both callers (`send-email.ts` and `trigger-email.ts`) explicitly build and pass through will not reach the BunMail API, breaking unsubscribe compliance for Gmail and other providers. Any attachments sent through the BunMail path are also silently discarded. Consider either adding `headers` and `attachments` support to the `sendViaBunMail` function and the BunMail API call, or documenting this as a known limitation so callers can handle it appropriately.</violation>
<violation number="2" location="apps/api/src/email/email-transport.ts:222">
P1: Scheduled emails are delivered immediately when SMTP is configured because this branch discards `scheduledAt`. Queue/defer SMTP sends until the requested time, or reject scheduled SMTP requests so users do not receive notifications early.</violation>
</file>
<file name="Dockerfile">
<violation number="1" location="Dockerfile:54">
P3: Docker builds can change unexpectedly because this resolves the latest global `tsx` outside the lockfile, and it bypasses the repository’s Bun-only dependency workflow. Use a pinned, Bun-managed `tsx` dependency instead.</violation>
</file>
<file name="apps/api/src/email/trigger-email.ts">
<violation number="1" location="apps/api/src/email/trigger-email.ts:75">
P3: The `console.log` on line 75 is the only informational log in this function, while the error path on line 95 uses `console.error`. In a NestJS application, consider using a structured `Logger` service for consistency, so that log levels and formatting are uniform across the codebase.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| - apps/app/.env | ||
| environment: | ||
| PGSSLMODE: disable | ||
| DATABASE_URL: postgresql://comp:comp@comp-postgres:5432/comp?sslmode=disable |
There was a problem hiding this comment.
P1: The self-hosted services cannot connect to the repository's Postgres container: these overrides replace each service's env-file URL with a host and credentials that do not exist in the provided database compose setup. Use the actual database service/credentials or parameterize DATABASE_URL so deployments can supply their own connection string.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose.yml, line 40:
<comment>The self-hosted services cannot connect to the repository's Postgres container: these overrides replace each service's env-file URL with a host and credentials that do not exist in the provided database compose setup. Use the actual database service/credentials or parameterize `DATABASE_URL` so deployments can supply their own connection string.</comment>
<file context>
@@ -30,17 +30,43 @@ services:
- apps/app/.env
+ environment:
+ PGSSLMODE: disable
+ DATABASE_URL: postgresql://comp:comp@comp-postgres:5432/comp?sslmode=disable
restart: unless-stopped
healthcheck:
</file context>
| } | ||
|
|
||
| if (isSmtpConfigured()) { | ||
| return sendViaSmtp({ |
There was a problem hiding this comment.
P1: Scheduled emails are delivered immediately when SMTP is configured because this branch discards scheduledAt. Queue/defer SMTP sends until the requested time, or reject scheduled SMTP requests so users do not receive notifications early.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/email/email-transport.ts, line 222:
<comment>Scheduled emails are delivered immediately when SMTP is configured because this branch discards `scheduledAt`. Queue/defer SMTP sends until the requested time, or reject scheduled SMTP requests so users do not receive notifications early.</comment>
<file context>
@@ -0,0 +1,243 @@
+ }
+
+ if (isSmtpConfigured()) {
+ return sendViaSmtp({
+ from: fromAddress,
+ to: toAddress,
</file context>
| } | ||
|
|
||
| if (isBunMailConfigured()) { | ||
| return sendViaBunMail({ |
There was a problem hiding this comment.
P1: When BunMail is configured, the sendHtmlEmail function silently drops both headers and attachments from the outgoing email payload. This means the RFC 8058 one-click unsubscribe headers (List-Unsubscribe / List-Unsubscribe-Post) that both callers (send-email.ts and trigger-email.ts) explicitly build and pass through will not reach the BunMail API, breaking unsubscribe compliance for Gmail and other providers. Any attachments sent through the BunMail path are also silently discarded. Consider either adding headers and attachments support to the sendViaBunMail function and the BunMail API call, or documenting this as a known limitation so callers can handle it appropriately.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/email/email-transport.ts, line 212:
<comment>When BunMail is configured, the `sendHtmlEmail` function silently drops both `headers` and `attachments` from the outgoing email payload. This means the RFC 8058 one-click unsubscribe headers (`List-Unsubscribe` / `List-Unsubscribe-Post`) that both callers (`send-email.ts` and `trigger-email.ts`) explicitly build and pass through will not reach the BunMail API, breaking unsubscribe compliance for Gmail and other providers. Any attachments sent through the BunMail path are also silently discarded. Consider either adding `headers` and `attachments` support to the `sendViaBunMail` function and the BunMail API call, or documenting this as a known limitation so callers can handle it appropriately.</comment>
<file context>
@@ -0,0 +1,243 @@
+ }
+
+ if (isBunMailConfigured()) {
+ return sendViaBunMail({
+ from: fromAddress,
+ to: toAddress,
</file context>
| networks: | ||
| default: | ||
| name: comp_network | ||
| external: true |
There was a problem hiding this comment.
P2: The networks.default overrides the project's default network to comp_network with external: true, which means this Docker network must already exist before docker-compose up will succeed. Running the compose file for the first time without a prior docker network create comp_network will fail with an opaque network-not-found error. Consider either dropping external: true so Docker Compose creates the network automatically, or adding a docker network create step to the project's setup documentation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose.yml, line 104:
<comment>The `networks.default` overrides the project's default network to `comp_network` with `external: true`, which means this Docker network must already exist before `docker-compose up` will succeed. Running the compose file for the first time without a prior `docker network create comp_network` will fail with an opaque network-not-found error. Consider either dropping `external: true` so Docker Compose creates the network automatically, or adding a `docker network create` step to the project's setup documentation.</comment>
<file context>
@@ -49,14 +75,30 @@ services:
+networks:
+ default:
+ name: comp_network
+ external: true
</file context>
| | { rejectUnauthorized: false }; | ||
|
|
||
| const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']); | ||
| const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', 'comp-postgres']); |
There was a problem hiding this comment.
P3: The new Docker hostname has no regression test, so an allowlist typo or later URL-parsing change can make self-hosted containers attempt TLS and fail to connect. Add a resolveSslConfig('postgresql://u:p@comp-postgres:5432/x', {}) case asserting undefined.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/src/ssl-config.ts, line 6:
<comment>The new Docker hostname has no regression test, so an allowlist typo or later URL-parsing change can make self-hosted containers attempt TLS and fail to connect. Add a `resolveSslConfig('postgresql://u:p@comp-postgres:5432/x', {})` case asserting `undefined`.</comment>
<file context>
@@ -3,7 +3,7 @@ export type SslConfig =
| { rejectUnauthorized: false };
-const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);
+const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', 'comp-postgres']);
function isLocalhostUrl(connectionString: string): boolean {
</file context>
| && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ | ||
| && apt-get install -y nodejs \ | ||
| && rm -rf /var/lib/apt/lists/* \ | ||
| && npm install -g tsx |
There was a problem hiding this comment.
P3: Docker builds can change unexpectedly because this resolves the latest global tsx outside the lockfile, and it bypasses the repository’s Bun-only dependency workflow. Use a pinned, Bun-managed tsx dependency instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Dockerfile, line 54:
<comment>Docker builds can change unexpectedly because this resolves the latest global `tsx` outside the lockfile, and it bypasses the repository’s Bun-only dependency workflow. Use a pinned, Bun-managed `tsx` dependency instead.</comment>
<file context>
@@ -26,28 +31,29 @@ COPY apps/portal/package.json ./apps/portal/
+ && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
+ && apt-get install -y nodejs \
+ && rm -rf /var/lib/apt/lists/* \
+ && npm install -g tsx
-# Run migrations against the combined schema published by @trycompai/db
</file context>
| }; | ||
|
|
||
| if (!process.env.TRIGGER_SECRET_KEY) { | ||
| console.log( |
There was a problem hiding this comment.
P3: The console.log on line 75 is the only informational log in this function, while the error path on line 95 uses console.error. In a NestJS application, consider using a structured Logger service for consistency, so that log levels and formatting are uniform across the codebase.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/email/trigger-email.ts, line 75:
<comment>The `console.log` on line 75 is the only informational log in this function, while the error path on line 95 uses `console.error`. In a NestJS application, consider using a structured `Logger` service for consistency, so that log levels and formatting are uniform across the codebase.</comment>
<file context>
@@ -30,16 +60,26 @@ export async function triggerEmail(params: {
+ };
+
+ if (!process.env.TRIGGER_SECRET_KEY) {
+ console.log(
+ 'TRIGGER_SECRET_KEY not set; sending email directly via configured transport',
+ );
</file context>
Add BunMail REST and SMTP email transports with direct send fallback when Trigger.dev is unset, wire the API service into docker-compose, and fix Prisma TLS for local Postgres hostnames.
What does this PR do?
Visual Demo (For contributors especially)
A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).
Video Demo (if applicable):
Image Demo (if applicable):
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Checklist
Summary by cubic
Enables self-hosted deployment by adding BunMail and SMTP email transports with automatic fallback and wiring the API service into Docker Compose. Also fixes Prisma TLS for local Postgres hosts and streamlines Docker builds and seeding.
New Features
TRIGGER_SECRET_KEYis unset, still adding one‑click unsubscribe headers.apps/apiservice indocker-compose(port 3333) with healthchecks and DB/env wiring; propagatesNEXT_PUBLIC_API_URLto app and portal.packages/dband dependents first, usestsxfor seeding, and increases Node memory; app runs on 3001 and portal on 3002.nodemailerand@types/nodemailertoapps/api.Migration
BUNMAIL_API_URL,BUNMAIL_API_KEY,BUNMAIL_FROM.SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS,SMTP_SECURE,SMTP_FROM.RESEND_*vars are set.NEXT_PUBLIC_API_URLfor app/portal builds and.env; API exposes:3333.comp-postgres; DB URL usessslmode=disableandPGSSLMODE=disable.comp-postgresis now treated as local for Prisma TLS.TRIGGER_SECRET_KEYunset to send emails directly; set it to use the queued task path.Written for commit b70915b. Summary will update on new commits.