Skip to content

Enable self-hosted deployment with BunMail, SMTP, and Docker fixes.#3492

Open
raedbrahem wants to merge 1 commit into
trycompai:mainfrom
1devspace:main
Open

Enable self-hosted deployment with BunMail, SMTP, and Docker fixes.#3492
raedbrahem wants to merge 1 commit into
trycompai:mainfrom
1devspace:main

Conversation

@raedbrahem

@raedbrahem raedbrahem commented Jul 23, 2026

Copy link
Copy Markdown

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?

  • Fixes #XXXX (GitHub issue number)
  • Fixes COMP-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

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):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

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

    • Adds BunMail REST and SMTP transports with priority order: BunMail → SMTP → Resend; sends directly when TRIGGER_SECRET_KEY is unset, still adding one‑click unsubscribe headers.
    • Introduces apps/api service in docker-compose (port 3333) with healthchecks and DB/env wiring; propagates NEXT_PUBLIC_API_URL to app and portal.
    • Improves Docker build: builds local packages/db and dependents first, uses tsx for seeding, and increases Node memory; app runs on 3001 and portal on 3002.
    • Adds nodemailer and @types/nodemailer to apps/api.
  • Migration

    • Email config (choose one):
      • BunMail: set BUNMAIL_API_URL, BUNMAIL_API_KEY, BUNMAIL_FROM.
      • SMTP: set SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE, SMTP_FROM.
      • Resend fallback: ensure existing RESEND_* vars are set.
    • Set NEXT_PUBLIC_API_URL for app/portal builds and .env; API exposes :3333.
    • Local Postgres: service must be comp-postgres; DB URL uses sslmode=disable and PGSSLMODE=disable. comp-postgres is now treated as local for Prisma TLS.
    • Optional Trigger.dev: leave TRIGGER_SECRET_KEY unset to send emails directly; set it to use the queued task path.

Written for commit b70915b. Summary will update on new commits.

Review in cubic

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.
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

@raedbrahem is attempting to deploy a commit to the Comp AI Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 drops scheduledAt, which can send scheduled emails immediately and create user-facing timing/compliance issues — defer SMTP sends until scheduledAt or explicitly reject scheduled SMTP requests.
  • In apps/api/src/email/email-transport.ts (sendHtmlEmail), the BunMail branch omits headers and attachments, 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.yml now requires an external comp_network, and packages/db/src/ssl-config.ts lacks 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

Comment thread docker-compose.yml
- apps/app/.env
environment:
PGSSLMODE: disable
DATABASE_URL: postgresql://comp:comp@comp-postgres:5432/comp?sslmode=disable

@cubic-dev-ai cubic-dev-ai Bot Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

}

if (isSmtpConfigured()) {
return sendViaSmtp({

@cubic-dev-ai cubic-dev-ai Bot Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

}

if (isBunMailConfigured()) {
return sendViaBunMail({

@cubic-dev-ai cubic-dev-ai Bot Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread docker-compose.yml
networks:
default:
name: comp_network
external: true

@cubic-dev-ai cubic-dev-ai Bot Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

| { 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']);

@cubic-dev-ai cubic-dev-ai Bot Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread Dockerfile
&& 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

@cubic-dev-ai cubic-dev-ai Bot Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

};

if (!process.env.TRIGGER_SECRET_KEY) {
console.log(

@cubic-dev-ai cubic-dev-ai Bot Jul 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants