stack/apps/e2e/tests/backend/endpoints/api/v1
BilalG1 74471d8d30
feat(emails): allow custom emails on shared server with dev wrapper (#1673)
## 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 -->
2026-06-26 15:44:44 -07:00
..
__snapshots__ Fix dev CI: docker prune missing template + cross-domain test failures (#1582) 2026-06-11 17:37:11 -07:00
auth fix: return 409 instead of 500 when signing up with duplicate email (#1637) 2026-06-22 17:29:08 -07:00
contact-channels feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
emails User ID filter for email outbox 2026-06-17 13:39:26 -07:00
integrations Rename STACK_* env vars to HEXCLAVE_* in env templates, with legacy dual-read (#1588) 2026-06-19 18:58:53 -07:00
internal Usage page performance improvements (#1650) 2026-06-24 12:25:20 -07:00
payments Better UX around hosted component errors 2026-06-04 17:27:04 -07:00
ai-query.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
analytics-config.test.ts Queries view (#1145) 2026-02-16 11:39:21 -08:00
analytics-events-batch.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
analytics-events.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
analytics-query.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
api-keys.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
auth-flows.test.ts Fix tests 2025-07-16 11:42:25 -07:00
check-feature-support.test.ts More backend endpoint implementations (#126) 2024-07-13 22:04:53 -07:00
connected-accounts.test.ts Fix GH tokens refresh & devtool tabs 2026-05-25 17:50:09 -07:00
data-vault.test.ts Speed up tests (#1063) 2025-12-28 11:25:04 -08:00
email-themes.test.ts feat(hexclave): PR 2 — visible rebrand (Hexclave brand goes public) (#1481) 2026-05-26 19:18:20 -07:00
external-db-sync-advanced.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
external-db-sync-basics.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
external-db-sync-high-volume.test.ts Turnstile integration for fraud protection (#1239) 2026-03-20 21:26:45 +00:00
external-db-sync-race.test.ts feat(hexclave): PR 1 — wire compatibility layer (invisible) (#1475) 2026-05-23 17:24:55 -07:00
external-db-sync-utils.ts feat(hexclave): PR 1 — wire compatibility layer (invisible) (#1475) 2026-05-23 17:24:55 -07:00
index.test.ts Update missing docs redirects 2026-06-03 17:14:22 -07:00
internal-metrics.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
internal-user-activity.test.ts feat(analytics): add route analytics heatmaps (#1520) 2026-06-15 12:06:16 -07:00
notification-preferences.test.ts Speed up tests (#1063) 2025-12-28 11:25:04 -08:00
oauth-providers.test.ts Fix OAuth provider disablement 2026-02-24 12:43:41 -08:00
project-permission-definitions.test.ts Config DB migration step 2 (#629) 2025-04-29 14:52:45 -07:00
project-permissions.test.ts Fix flaky tests and preexisting CI failures (#1443) 2026-05-20 10:00:11 -07:00
projects.test.ts Update missing docs redirects 2026-06-03 17:14:22 -07:00
render-email.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
restricted-users.test.ts Sign up rules (#1138) 2026-02-03 11:08:24 -08:00
risk-scores.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
send-email.test.ts feat(emails): allow custom emails on shared server with dev wrapper (#1673) 2026-06-26 15:44:44 -07:00
session-replays.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
stripe-webhooks.test.ts feat(payments): quick-ack + idempotent webhooks (#1664) 2026-06-24 09:30:38 -07:00
support.test.ts [Apps] Adding support app alpha and dogfooding (#1368) 2026-05-13 11:36:11 -05:00
team-invitations.test.ts Improved PKCE support 2026-06-11 10:28:14 -07:00
team-member-profiles.test.ts Speed up tests (#1063) 2025-12-28 11:25:04 -08:00
team-memberships.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
team-permission-definitions.test.ts refactor(dashboard): unify AI chat surfaces on assistant-ui Thread (#1427) 2026-05-15 14:21:00 -07:00
team-permissions.test.ts Fix flaky tests and preexisting CI failures (#1443) 2026-05-20 10:00:11 -07:00
teams.test.ts feat(hexclave): PR 1 — wire compatibility layer (invisible) (#1475) 2026-05-23 17:24:55 -07:00
token-refresh-events.test.ts feat(hexclave): PR 3 — native @hexclave/* source rename + delete dual-publish wiring (#1482) 2026-05-29 15:21:59 -07:00
unsubscribe-link.test.ts feat(hexclave): PR 1 — wire compatibility layer (invisible) (#1475) 2026-05-23 17:24:55 -07:00
users-primary-email.test.ts fix: clearer error when changing email to one already used for auth (#1569) 2026-06-15 13:55:26 -07:00
users.test.ts User page email filtering (#1668) 2026-06-26 12:27:24 -07:00