mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
## What Custom emails / templates / drafts sent through Hexclave's **shared (development) email server** are no longer blocked with `RequiresCustomEmailServer`. They are now allowed, but their **subject and body are wrapped** at send time with a notice that this is a development email from Hexclave, so unexpected recipients know they can safely ignore it. The wrapper only applies to **project-defined content addressed to the project's own users**. Hexclave's own default-template emails (verification, password reset, magic link, etc.) and system notifications (credential-scanning alerts, internal feedback) are sent **verbatim**. ## How - **[send-email/route.tsx](apps/backend/src/app/api/latest/emails/send-email/route.tsx)** — removed the `RequiresCustomEmailServer` throw that blocked the shared server. - **[emails.tsx](apps/backend/src/lib/emails.tsx)** — added `wrapSharedDevEmail()` (prefixes the subject with `[Hexclave dev email]` and prepends a notice banner to HTML/text) and `isCustomEmailForSharedServer(recipient, createdWith, templateId)`. - **[email-queue-step.tsx](apps/backend/src/lib/email-queue-step.tsx)** — applies the wrapper at send time, gated on `emailConfig.type === "shared"` **and** the email being project-defined custom content. Applying it at send time reliably wraps both the subject (from `overrideSubject` or the template's `<Subject>`) and the rendered HTML. ### What counts as "wrap-eligible" `isCustomEmailForSharedServer` returns true only when **all** hold: 1. the email is addressed to one of the project's own users (recipient type is not `custom-emails`), **and** 2. it is a draft, a custom template, or raw HTML — i.e. **not** one of the built-in `DEFAULT_TEMPLATE_IDS`. Condition (1) exempts Hexclave's own system senders (credential-scanning revoke, internal feedback) which send raw HTML to bare addresses via `custom-emails` and would otherwise be mis-classified as project content. This was a bug caught in review — a leaked-API-key security alert to a shared-server customer would have been prefixed `[Hexclave dev email]` with a "you can safely ignore it" banner. The recipient type is already persisted on the outbox row, so no schema change was needed. ## Tests - **send-email.test.ts** — replaced the old "400 on shared config" test with two new tests: (a) a custom email on the shared server is delivered with the `[Hexclave dev email]` subject prefix + notice banner, and (b) a **default template** (`sign_in_invitation`) on the shared server is delivered **verbatim** (no prefix, no banner) — pinning the core safety contract. - **js/email.test.ts** — flipped the "throws RequiresCustomEmailServer" test to assert the send now resolves. Verified locally against a full stack: - ✅ `send-email.test.ts` — 18/18 - ✅ `js/email.test.ts` — 12/12 - ✅ `password/send-reset-code.test.ts` — passes (default templates on shared server stay unwrapped) ## Known limitations (intentional scope) - **Template CRUD still blocked on the shared server.** `internal/email-templates` routes still throw `RequiresCustomEmailServer`, so a shared-server project can send raw HTML / a default template via the API but cannot create or edit a *saved* custom template. Sending arbitrary HTML is unaffected; only the saved-template editor remains gated. - **A project can send a (project-edited) default template unwrapped** by calling `send-email` with a `template_id` equal to a built-in `DEFAULT_TEMPLATE_IDS` value. Low impact (requires a server key, limited upside), noted for awareness. ## Note: freestyle-mock fix included [freestyle-mock/Dockerfile](docker/dependencies/freestyle-mock/Dockerfile) now also accepts `/execute/v3/script`. The `freestyle` SDK bump in #1654 moved to `/v3`, but the mock only served `/v1`+`/v2`, so **all** local email rendering 404'd (pre-existing `dev` breakage, not from this feature). The v3 request/response is identical to v2. Happy to split this into its own PR if preferred. Out of scope: `emails/email-queue.test.ts` has 2 pre-existing snapshot failures (`margin:0` vs recorded `margin:0rem`, a `@react-email/components` version drift in the mock) — those tests use a custom email server, so this PR's shared-only code path never runs for them. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Email sending can now proceed when using a shared email server. * Development-style wrapping is applied to eligible shared-server custom email content, including HTML notice injection. * **Bug Fixes** * Removed the previous blocking “requires custom email server” behavior for shared-server configurations. * Default-template emails over the shared server are no longer wrapped. * **Tests** * Updated end-to-end and JS email tests to validate both wrapped custom-email behavior and unwrapped default-template behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|---|---|---|
| .. | ||
| backend | ||
| dependencies | ||
| dev-postgres-replica | ||
| dev-postgres-with-extensions | ||
| local-emulator | ||
| mock-oauth-server | ||
| server | ||