CopilotKit / CopilotKit/outpost

templates/ never reaches the production image, so every email send fails with emailSent: false

Open
#253 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area: infrastructure bug
Dominant language
TypeScript
Stars
7
Forks
3
Avg merge
7d 16h
Merged PRs (30d)
15

Description

Every email template is absent from the production web image, so every send fails and returns emailSent: false. This is on main today; no PR introduces it and no open issue covers it.

Raised by @NathanTarbert on #248 as "worth confirming against a built image". I confirmed it against the tree.

The chain

  1. templates/ is seven .md files at the repo root — digest, escalation, invite, sla-breach, ticket-created, ticket-resolved, welcome.
  2. .dockerignore:7 is *.md, and :8 re-includes only !README.md. The templates never enter the build context, so the pruner's COPY . . cannot pick them up.
  3. No Dockerfile in the repo copies templates/grep -rn "templates" --include="Dockerfile*" . returns nothing. apps/web/Dockerfile's runner stage copies .next/standalone, .next/static, public, the Prisma artifacts and start.sh.
  4. findTemplatesDir() (packages/outpost/shared/src/templates/loader.ts:50-68) walks four candidate paths, finds none, and falls back to join(process.cwd(), 'templates').
  5. loadFromFilesystem (:70-82) — readFileSync throws, the catch returns null.
  6. sendEmail (packages/outpost/shared/src/email/sender.ts:166-168) returns { success: false, error: 'Template "invite" not found', method: 'console' }.
  7. apps/web/src/app/api/team/invite/route.ts:92-95 turns that into a console.warn and NextResponse.json({ ...responseData, emailSent: false }).

Impact

sendEmail has exactly two live call sites, both invites (api/team/invite/route.ts:72, api/team/invite/resend/route.ts:52). So in production, inviting a teammate creates the member row and sends nothing — the invitee has to be sent the inviteUrl by hand. The remaining six templates are unreachable for the same reason if anything starts using them.

It fails as a returned value rather than a throw, and the only signal is one console.warn plus a JSON field. Whether the UI surfaces emailSent: false is worth checking as part of this.

Why it has stayed hidden

The 35 template tests run from apps/web, where join(cwd, '..', '..', 'templates') resolves against the real repo root. They pass for a reason that does not exist inside the image.

Fix

Either un-ignore the directory and copy it into the runner stage:

# .dockerignore
*.md
!README.md
!templates/*.md
# apps/web/Dockerfile, runner stage
COPY --from=installer --chown=outpost:outpost /app/templates ./templates

…or stop loading them from disk at runtime — import them as modules so bundling carries them, which also removes findTemplatesDir()'s four-candidate CWD walk.

Whichever way, this wants a test that fails when the templates are not resolvable, rather than one that passes because it runs from a checkout.

Not verified

I read the Dockerfiles and .dockerignore, not a built image. Next's standalone output tracing will not pick up a runtime-computed readFileSync path, so I do not think it saves this — but docker build on apps/web then ls templates in the runner settles it in a minute, and I would rather be corrected than have this taken on my reading.

Related

  • #248 lists this as open item 2 against the template-preview work.
  • #226 (stored XSS in the template preview) and #215 (Postmark DNS) both assume this path works.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with .dockerignore, apps/web/Dockerfile, and packages/outpost/shared/src/templates/loader.ts, then build the apps/web image and inspect the runner's templates directory. Trace the invite flow through sender.ts and the two invite routes. Done means the production image resolves all seven templates and the invite response no longer reports emailSent: false because a template is missing.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, nextjs, typescript
Domain
backend, infrastructure
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.