Skip to content

fix(invite): resolve invite email base URL from platform.baseUrl setting#942

Open
mdubel wants to merge 1 commit into
mainfrom
fix/invite-base-url
Open

fix(invite): resolve invite email base URL from platform.baseUrl setting#942
mdubel wants to merge 1 commit into
mainfrom
fix/invite-base-url

Conversation

@mdubel

@mdubel mdubel commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Problem

On self-hosted deployments, workspace-invite and resend-invite emails link to http://localhost:PORT whenever NEXT_PUBLIC_PLATFORM_URL isn't set — or is set in .env but not enumerated in the docker-compose.prod.yml per-service environment: passthrough, so it never reaches the container. The base URL was read once from process.env at service-singleton construction and baked into the email adapter, so a misconfigured host couldn't be corrected without an env edit + redeploy. Observed on phuse.mediforce.ai.

Fix

Resolve the base URL at send time instead of at construction. Precedence:

platform.baseUrl setting → NEXT_PUBLIC_PLATFORM_URL env → http://localhost:PORT

The env/localhost fallback is unchanged, so behaviour is byte-identical when the setting is unset. When set, operators fix a wrong host with one CLI call — no .env edit, no compose passthrough, no SSH:

mediforce config set platform.baseUrl https://<host>

Resolution is deployment-global on purpose: every workspace on a box shares the host, and the workspace handle is already the URL path segment (${baseUrl}/${handle}), so a per-workspace override would just be the same value duplicated N times (and re-broken on every new workspace).

Changes

  • contract/config.tsPLATFORM_BASE_URL_SETTING_KEY constant (platform.baseUrl)
  • handlers/_helpers.tsresolveConfiguredBaseUrl(scope): reads scope.system.platformSettings, strips trailing slash, returns undefined when unset/blank (so a cleared setting falls back instead of sending an empty URL)
  • services/invite-notification.ts — optional baseUrl on SendInviteEmailInput / SendWorkspaceNotificationEmailInput
  • handlers/users/invite-user.ts + resend-invite.ts — resolve baseUrl, pass into the notify calls (spread only when defined)
  • services/platform-services.tsEmailInviteNotificationService uses input.baseUrl ?? this.appUrl for both email types

Reuses the existing generic config get/set (handler + mediforce config CLI) — no new endpoint or command.

Tests

Handler tests for base-URL pass-through: setting present, trailing-slash trimmed, unset (omitted), whitespace-cleared (omitted → falls back). pnpm --filter @mediforce/platform-api exec vitest run src/handlers/users/__tests__/invite-user.test.ts src/handlers/users/__tests__/resend-invite.test.ts26 passing. No new typecheck errors.

Post-merge fix for phuse

After this rebuilds on the box:

pnpm exec mediforce config set platform.baseUrl https://phuse.mediforce.ai

🤖 Generated with Claude Code

Workspace-invite and resend-invite emails linked to http://localhost:PORT
whenever NEXT_PUBLIC_PLATFORM_URL wasn't set (or wasn't passed through
docker-compose to the container). The base URL was read once from
process.env at service-singleton construction and baked into the email
adapter, so it couldn't be corrected without a redeploy.

Resolve the base URL at send time instead: a new `platform.baseUrl`
platform setting takes precedence, falling back to
NEXT_PUBLIC_PLATFORM_URL and then localhost exactly as before. Operators
fix a misconfigured host with one CLI call, no SSH:

    mediforce config set platform.baseUrl https://<host>

- contract/config.ts: PLATFORM_BASE_URL_SETTING_KEY constant
- handlers/_helpers.ts: resolveConfiguredBaseUrl(scope) (reads
  platformSettings, trims trailing slash, undefined when unset/blank)
- services/invite-notification.ts: optional baseUrl on both email inputs
- handlers/users/{invite-user,resend-invite}.ts: resolve + pass baseUrl
- services/platform-services.ts: adapter prefers input.baseUrl ?? appUrl

Behaviour is byte-identical when the setting is unset. Resolution is
deployment-global (every workspace shares the host; the handle is the
path segment). Covered by handler tests for the set / trailing-slash /
unset / blank cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
mediforce Ignored Ignored Jul 17, 2026 8:47pm

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Updates the invite/resend email flow in @mediforce/platform-api to resolve the public base URL from a deployment-level platform.baseUrl setting at send time (falling back to NEXT_PUBLIC_PLATFORM_URL and then localhost), so self-hosted deployments can correct invite link hosts without rebuilding/redeploying.

Changes:

  • Added platform.baseUrl setting key constant and a handler helper to resolve + normalize it (trim + strip trailing slash, treat blank as unset).
  • Passed an optional baseUrl override through invite-user / resend-invite into the invite notification service.
  • Updated the email adapter to use the per-send baseUrl override and added handler tests covering configured/unset/whitespace-cleared cases, plus a changelog entry.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/platform-api/src/contract/config.ts Adds PLATFORM_BASE_URL_SETTING_KEY (platform.baseUrl) used to drive invite link host resolution.
packages/platform-api/src/handlers/_helpers.ts Adds resolveConfiguredBaseUrl(scope) to read + normalize the configured base URL from platform settings.
packages/platform-api/src/services/invite-notification.ts Extends invite notification inputs with optional baseUrl override.
packages/platform-api/src/handlers/users/invite-user.ts Resolves configured base URL and conditionally passes it into invite/workspace-notification emails.
packages/platform-api/src/handlers/users/resend-invite.ts Resolves configured base URL and conditionally passes it into resend-invite emails.
packages/platform-api/src/services/platform-services.ts Updates the email notification adapter to prefer input.baseUrl over the construction-time appUrl.
packages/platform-api/src/handlers/users/tests/invite-user.test.ts Adds coverage for baseUrl pass-through, trailing-slash trimming, and unset/whitespace-cleared behavior.
packages/platform-api/src/handlers/users/tests/resend-invite.test.ts Adds coverage for baseUrl pass-through in resend-invite.
CHANGELOG.md Documents the new platform.baseUrl-first resolution behavior for invite/resend email links.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3 to +9
/**
* Platform-settings key holding the deployment's public base URL (e.g.
* `https://phuse.mediforce.ai`). Read at invite time to build workspace/login
* links; set via `mediforce config set platform.baseUrl <url>`. Falls back to
* `NEXT_PUBLIC_PLATFORM_URL` then localhost when unset.
*/
export const PLATFORM_BASE_URL_SETTING_KEY = 'platform.baseUrl';
Comment on lines 225 to +226
async sendInviteEmail(input: SendInviteEmailInput): Promise<void> {
const appUrl = input.baseUrl ?? this.appUrl;
Comment on lines 238 to +239
async sendWorkspaceNotificationEmail(input: SendWorkspaceNotificationEmailInput): Promise<void> {
const appUrl = input.baseUrl ?? this.appUrl;
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