Summary: The elixir-backend serves both the 7Mind and 7Sleep products from a single codebase and deployment, using request-time routing to give each brand its own API surface and domain logic. Sources: direct code inspection (lib/backend/web/, config/runtime/prod.ex) Last updated: 2026-05-15


The problem it solves

7Mind (meditation app) and 7Sleep (sleep app) share most infrastructure: authentication, billing, content delivery, practice tracking. Maintaining two separate backends would duplicate this. Instead, one backend handles both, with routing logic deciding which brand context a given request operates in.

How routing works

The Public API endpoint (port 4100) inspects incoming requests to determine brand context:

  • By hostname: 7mind.7mind.de routes to SevenMindRouter; api.7sleep.de routes to SevenSleepRouter
  • By header: the x-app header can explicitly declare the brand

Both routers expose the same route namespaces (/practice/*, /content/*, /user/*, etc.) but may have brand-specific implementations behind them.

Chargebee billing is configured with separate namespaces and API keys per brand (SEVENMIND_CHARGEBEE_NAMESPACE, SLEEP_CHARGEBEE_NAMESPACE). Rudderstack analytics uses separate write keys per brand. Braze uses separate app IDs. Each brand’s data is isolated at the service-integration level even though the Elixir code is shared.

Content Provider pattern

The content domain uses a Provider behaviour (Content.SevenSleep.Provider / Content.SevenMind.Provider) to abstract CMS differences between the two apps. Brand-specific content configuration is injected at runtime, not compiled in.

What agents should know

  • When adding a feature that behaves differently per brand, use the existing routing layer and Provider pattern rather than introducing a new branching mechanism.
  • Env vars for external services come in pairs — one per brand. Always check both when debugging billing, analytics, or content issues.
  • The admin panel (port 4200) is brand-agnostic; it operates across both brands.
  • Mobile clients identify themselves — agents editing API handlers should assume either brand can call any shared endpoint.

From 7mind-mobile-apps-monorepo

Despite the plural “mobile-apps-monorepo” name, the Flutter codebase only ships the 7Mind brand. There is no 7Sleep app code in this repo. As a consequence:

  • The x-app header sent by mobile clients only ever identifies 7Mind from this repo
  • 7Sleep mobile traffic, if it exists, must come from a different repository or be web-only
  • Per-brand mobile configuration (Chargebee namespace, Rudderstack write key, Braze app ID) is single-valued, not selected at runtime
  • Future agents should not assume a “7Sleep flavor” can be added without significant rewiring (new flavors, new Firebase project, new SSO client IDs, new push certificates)

From nuxt-website

The web does not use request-time brand routing. It uses build-time multiplexing via a THEME env var: one codebase produces two distinct Docker images, one per brand. See theme-build-multiplexing for the full pattern. Implications for backend agents:

  • The 7mind web ships with the SevenMind brand baked in; 7sleep web ships separately. Each calls magic.7mind.de or magic.7sleep.de respectively, so the backend’s hostname-based routing kicks in naturally.
  • The web does not send an x-app header. Brand selection on the backend for web requests is hostname-driven.
  • Web requests can come from 7mind.de, 7sleep.de, plus localized TLDs (.fr, .it, .nl, .es, .uk, .ca, etc.) and migration / preview hosts (6mind.de, 8mind.de, *.review.6mind.de). All of these resolve to the same brand pair (7mind family → SevenMind, 7sleep family → SevenSleep). The backend’s router needs to recognize the whole family, not just the canonical .de host.