# Zimphony Webmail Product Editions

This repository is being shaped as Zimphony Webmail, an installable white-label webmail product for organizations that already run Zimbra.

The customer bundle is webmail-first and on-prem. SaaS stays in the codebase as
a future deployment mode, not the primary selling path for the edition packs.

The product should ship as one codebase with one default path per installation, then enable optional provider add-ons through configuration.

Across every package, `Mail` and `Contacts` use Zimbra Mail and Zimbra Contacts
as their data providers. Customer-facing UI keeps the neutral labels `Mail`
and `Contacts`; technical setup and troubleshooting documentation may name
Zimbra when the provider distinction is necessary.

The edition is selected by `PRODUCT_EDITION` in the backend environment:

- `mail_only`
- `mail_google`
- `mail_native_zimbra`

The commercial tier is selected by `PRODUCT_TIER`:

- `free`
- `shared_webmail`
- `zimbra_native`
- `google_connect`

If `PRODUCT_TIER` is omitted, the backend preserves existing installs by inferring:

- `mail_only` -> `shared_webmail`
- `mail_google` -> `google_connect`
- `mail_native_zimbra` -> `zimbra_native`

Free Edition must be explicit with `PRODUCT_TIER=free`.
Free and Shared Webmail are encoded packages and should use
`SOURCE_SKU=encoded_unlimited`. Native-Zimbra and Google-Connect customer
packages must use encoded customer delivery; `full_source` or source-code
packages are internal/dev only and are not customer-delivery artifacts.
Use `SOURCE_SKU=encoded_per_mailbox` for paid Native-Zimbra and Google-Connect
capacity SKUs. Capacity is enforced by the license entitlement, not by different
source code.
Docker release packages for customer delivery must use the encoded backend
packaging path so the customer zip does not include the plain Python backend
source.
For encoded Docker packages, `ZIMPHONY_PACKAGE_EDITION_LOCK`,
`ZIMPHONY_PACKAGE_TIER_LOCK`, and `ZIMPHONY_PACKAGE_SOURCE_SKU_LOCK` are the
effective package locks and win over manually edited `PRODUCT_EDITION`,
`PRODUCT_TIER`, and `SOURCE_SKU` values at runtime.

Paid edition packages require first-run activation with the license server. If
the license server is offline before first activation, the installation may run
in a 15-day activation grace period. Unknown, rejected, suspended, expired, or
revoked licenses block first activation. After a deployment has activated
successfully, license server outages, support-plan expiry, over-deployment, and
mailbox quota excess remain warnings and must not stop core mail usage.
Paid installations should set `LICENSE_CHECK_SIGNING_SECRET`; when configured,
Zimphony Webmail signs license check requests with `X-Zimphony-Timestamp` and
`X-Zimphony-Signature`. The license server can require signed checks only after
updated Zimphony Webmail packages have rolled out.
Compliance warnings escalate without stopping mail: 0-30 days admin warning,
31-60 days user banner, 61-90 days stronger user banner, and 90+ days a
full-screen notice after login that requires Continue before entering the
mailbox.

## Product Goal

- Sell packaged, encoded deployments to customers who will install them on their own infrastructure.
- Hide Zimbra from the end-user experience.
- Keep mail as the core product.
- Core mail includes server-side scheduled sending through Zimbra drafts, available in every edition.
- Drafts, including scheduled messages waiting to send, are always listed as individual messages and never grouped as conversations.
- Advanced search executes Zimbra search query syntax through `SearchRequest`; the existing simple search remains the default flow.
- Saved searches are Zimbra search folders managed through `GetSearchFolderRequest`, `CreateSearchFolderRequest`, `ModifySearchFolderRequest`, and folder actions. They are not stored in the application database or browser storage.
- High Priority and Read Receipt metadata are stored with Zimbra messages and drafts. Recipient acknowledgements use `SendDeliveryReportRequest` rather than an application-generated reply.
- Offer optional calendar and drive add-ons.
- Treat platform admin, superadmin, and billing flows as SaaS-only surfaces; keep them out of the current installable edition UI.

## Customer Package Naming Policy

Customer-facing package names must match the SKU the customer purchased, even
when the underlying encoded build is shared across multiple capacity SKUs.
Capacity is enforced by the license entitlement from LOR, not by shipping
different source code per capacity. Package artifact names must never include
secrets, license keys, customer PII, raw order IDs, API tokens, or passwords.

Use `<build>` for the build identifier in examples. It may be a timestamp,
release version, build ID, or approved version/build combination.

| Canonical SKU | Customer artifact name pattern | Edition / tier | Capacity | License |
| --- | --- | --- | --- | --- |
| `webmail-free` | `zimphony-webmail-free-<build>.zip` | `mail_only` / `free` | Free tier | License-free |
| `webmail-shared` | `zimphony-webmail-shared-<build>.zip` | `mail_only` / `shared_webmail` | Unlimited active mailboxes from Zimphony side | Paid deployment license |
| `native-zimbra-starter-50` | `zimphony-native-zimbra-starter-50-<build>.zip` | `mail_native_zimbra` / `zimbra_native` | 50 mailboxes | Paid per-mailbox entitlement |
| `native-zimbra-business-100` | `zimphony-native-zimbra-business-100-<build>.zip` | `mail_native_zimbra` / `zimbra_native` | 100 mailboxes | Paid per-mailbox entitlement |
| `native-zimbra-professional-250` | `zimphony-native-zimbra-professional-250-<build>.zip` | `mail_native_zimbra` / `zimbra_native` | 250 mailboxes | Paid per-mailbox entitlement |
| `native-zimbra-enterprise-500` | `zimphony-native-zimbra-enterprise-500-<build>.zip` | `mail_native_zimbra` / `zimbra_native` | 500 mailboxes | Paid per-mailbox entitlement |
| `native-zimbra-scale-750` | `zimphony-native-zimbra-scale-750-<build>.zip` | `mail_native_zimbra` / `zimbra_native` | 750 mailboxes | Paid per-mailbox entitlement |
| `native-zimbra-contact-sales` | No fixed public artifact. Prepare only after sales approval. | `mail_native_zimbra` / `zimbra_native` | 1000+ / custom | Paid custom entitlement |
| `google-connect-starter-50` | `zimphony-google-connect-starter-50-<build>.zip` | `mail_google` / `google_connect` | 50 mailboxes | Paid per-mailbox entitlement |
| `google-connect-business-100` | `zimphony-google-connect-business-100-<build>.zip` | `mail_google` / `google_connect` | 100 mailboxes | Paid per-mailbox entitlement |
| `google-connect-professional-250` | `zimphony-google-connect-professional-250-<build>.zip` | `mail_google` / `google_connect` | 250 mailboxes | Paid per-mailbox entitlement |
| `google-connect-enterprise-500` | `zimphony-google-connect-enterprise-500-<build>.zip` | `mail_google` / `google_connect` | 500 mailboxes | Paid per-mailbox entitlement |
| `google-connect-scale-750` | `zimphony-google-connect-scale-750-<build>.zip` | `mail_google` / `google_connect` | 750 mailboxes | Paid per-mailbox entitlement |
| `google-connect-contact-sales` | No fixed public artifact. Prepare only after sales approval. | `mail_google` / `google_connect` | 1000+ / custom | Paid custom entitlement |

Rules:

- Free is license-free. Free installs must leave `LICENSE_SERVER_URL` and
  `ZIMPHONY_LICENSE_KEY` blank.
- Webmail Shared is paid but uses unlimited active mailboxes from Zimphony side.
- Native-Zimbra and Google-Connect use the 50 / 100 / 250 / 500 / 750 paid
  capacity ladder.
- 1000+ is Contact Sales only and must not be published as a fixed public SKU
  artifact.
- For 1000+ customers, delivery is a custom sales-approved encoded artifact plus
  LOR entitlement, not a public helper script.
- Capacity is license-side entitlement data. It is not enforced by shipping a
  different source-code package.
- The underlying encoded build may be shared, but the customer-facing artifact
  name must match the purchased SKU.
- `full_source` and source-code packages are internal/dev only, not customer
  delivery artifacts.
- Public package docs must not advertise a stale 25-mailbox paid SKU or an
  unlimited customer SKU for Native-Zimbra or Google-Connect.

## Commercial Tiers

### Tier 1: Free Edition

Configuration:

- `PRODUCT_EDITION=mail_only`
- `PRODUCT_TIER=free`
- `SOURCE_SKU=encoded_unlimited`

Includes:

- Mail
- Contacts
- 2FA and account security surfaces

Visible but locked with a small `Pro` badge:

- Mail folder sharing
- Contact folder sharing
- Calendar
- Tasks
- Briefcase

Locked features must remain visible enough for upgrade discovery, but clicking them must not open the module, modal, or API action.

### Tier 2: Shared Webmail

Configuration:

- `PRODUCT_EDITION=mail_only`
- `PRODUCT_TIER=shared_webmail`
- `SOURCE_SKU=encoded_unlimited`

Includes:

- Mail
- Contacts
- 2FA and account security surfaces
- Mail folder sharing
- Contact folder sharing

Calendar, Tasks, and Briefcase/Drive are hidden in this tier to keep the UI focused.

### Tier 3: Native-Zimbra / Complete Workspace

Configuration:

- `PRODUCT_EDITION=mail_native_zimbra`
- `PRODUCT_TIER=zimbra_native`
- `SOURCE_SKU=encoded_per_mailbox` for customer delivery

Includes:

- Mail
- Contacts
- Mail and contact folder sharing
- Native calendar
- Native tasks
- Native briefcase / file storage

Customer SKUs use the 50 / 100 / 250 / 500 / 750 capacity ladder documented in
the Customer Package Naming Policy. 1000+ mailbox deployments are Contact Sales
only.

### Native-Zimbra Capacity Entitlement

Configuration:

- `PRODUCT_EDITION=mail_native_zimbra`
- `PRODUCT_TIER=zimbra_native`
- `SOURCE_SKU=encoded_per_mailbox`

The underlying encoded build may be shared across Native-Zimbra capacity SKUs,
but the customer-facing artifact name must match the purchased SKU. The mailbox
quota comes from the license entitlement (`license_model=deployment_mailbox`,
`mailbox_quota=50|100|250|500|750`), not from different source code.

Includes the same Mail, Contacts, Native Calendar, Tasks, and Briefcase
capabilities across each Native-Zimbra capacity SKU.

### Tier 4: Google-Connect

Configuration:

- `PRODUCT_EDITION=mail_google`
- `PRODUCT_TIER=google_connect`
- `SOURCE_SKU=encoded_per_mailbox` for customer delivery

Includes:

- Mail
- Contacts
- Mail and contact folder sharing
- Google Calendar
- Google Tasks
- Google Drive

Customer SKUs use the 50 / 100 / 250 / 500 / 750 capacity ladder documented in
the Customer Package Naming Policy. 1000+ mailbox deployments are Contact Sales
only. The underlying encoded build may be shared across Google-Connect capacity
SKUs, but the customer-facing artifact name must match the purchased SKU and
capacity remains license-side entitlement data.

## Technical Editions

### Edition 1: Mail Only

The baseline product for every installation.

Includes:
- Webmail UI
- Login
- Inbox, folders, search, read pane, compose, attachments
- Contacts
- Signatures
- Filters
- Mobile and tablet layouts
- Branding and theme configuration
- Zimbra mail integration only

Mail rendering rule:

- Message reading must follow MIME/RFC structure, not UI heuristics.
- Real attachment leaf parts must render as downloadable attachments.
- Inline `cid:` resources and embedded body assets must remain inline unless the MIME tree clearly marks them as downloadable.
- Backend MIME classification is the source of truth; frontend should not re-infer attachment meaning independently.

Conversation / thread rule:

- Expanding a thread must use the clicked message `uid` as the reader target.
- The backend resolves the conversation id from that message uid, instead of trusting the row key as the conversation key.
- Thread content should ignore Trash items when counting or rendering members.
- The thread row key in the UI can use `conversation_id || uid`, but the detail fetch must stay message-uid driven.

This edition should be the default release candidate before any add-ons are expanded further.

### Edition 2: Mail + Google Add-on

Optional add-on for customers who want Google-backed productivity features.

Includes:
- Mail
- Contacts
- Google Calendar provider
- Google Tasks provider
- Google Drive provider
- Existing Google OAuth / attachment / calendar / task flows

Keep this as an add-on so the core mail product remains clean.

Implementation notes:

- Use `PRODUCT_EDITION=mail_google` for installations that should expose Google add-ons.
- Keep Google provider wiring behind capability checks and OAuth callbacks.
- Keep the core mail screens vendor-neutral.
- See [Google add-on integration](google-addon-integration.md) for the current backend/frontend split and setup checklist.

### Edition 3: Mail + Native-Zimbra Add-on

Optional add-on for customers who want everything to stay inside their Zimbra stack.

Includes:
- Mail
- Contacts
- Native-Zimbra calendar provider
- Native-Zimbra task provider
- Native-Zimbra briefcase / file storage provider
- Zimbra-side calendar, task, and file flows exposed through the same UI

Implementation note:

- `mail_native_zimbra` now enables the calendar, tasks, and drive capability flags in the product profile.
- The UI should surface `Calendar`, `Tasks`, and `Briefcase` tabs directly; do not rely on a generic `Zimbra` tab as the primary user entry point.
- The backend routes should resolve to the Zimbra SOAP provider for calendar, task, and briefcase operations when this edition is selected.
- Native task sharing should use the same folder share model as calendar and briefcase, but stay inside the task route and task UI surface.
- Native task lists should be fetched from the Zimbra task folder tree and presented as a folder picker in the task UI.
- Native calendar sharing uses the Zimbra folder share model on the calendar system folder.
- Native briefcase browsing should use the Zimbra document folder tree, with folder creation enabled where SOAP support is available.
- Native-Zimbra shares should stay resource-specific in the backend: mail folder sharing in mail routes, calendar sharing in calendar/native-zimbra share routes, task sharing in task/native-zimbra share routes, and briefcase sharing in drive/native-zimbra share routes.

## Architecture Rules

### 1. One codebase

- Keep one frontend.
- Keep one backend.
- Use provider adapters, not separate apps.

### Login Page Customization

The `/login` experience is a frontend customization surface, not part of the
Nuitka backend encoding boundary. Treat customer-specific login pages as a
paid branding/customization service that can vary by accessed domain or tenant.
The backend should expose branding and capability data; the frontend should
render the matching login page shell, logo, tagline, background, support link,
and optional customer campaign copy for that domain.

Tenant administrators manage this surface from `/admin` -> `Login Designer`.
Built-in themes and custom designs are separate saved modes, so switching modes
does not discard the other mode's settings. Custom design is intentionally
template-based: four fixed layouts, two background colors, login-box transparency
from 0 to 30 percent, and one PNG/JPEG/WEBP background image (12 MB maximum). The recommended upload dimensions are shown
on each template. This is shared core branding behavior in every edition.

Generate login artwork from `frontend/` with
`node scripts/optimize-public-images.mjs`. Runtime login pages use full-size
WebP assets, while Login Designer uses separate thumbnail WebP assets. Raster
logo and background uploads are resized and converted to WebP in the admin
browser before upload. Docker bundles exclude superseded login PNG sources and
the unused JPG logo source.

### 2. One default mode per installation

Each customer installation should choose one primary mode:
- Mail only
- Mail + Google add-on
- Mail + Native-Zimbra add-on

Do not require every provider to be enabled together.

### 3. Provider abstraction

All external integrations should go through interfaces such as:
- `MailProvider`
- `CalendarProvider`
- `DriveProvider`

The UI should only know capabilities, not vendor names.

### 4. Capability flags

Drive and calendar features should be shown only if the selected edition supports them.

Example capabilities:
- `calendar_read`
- `calendar_write`
- `drive_browse`
- `drive_attach`
- `large_attachment_share`

### 5. White-label UI

- Do not expose Zimbra in labels, menus, or empty states.
- Use product terminology like Mail, Calendar, Drive, Attachments, and Shared links.
- Keep branding configurable.

### 6. Backend addon split

- Keep native Zimbra transport code in `backend/app/addons/zimbra.py`.
- Keep native Zimbra edition helpers in `backend/app/addons/native_zimbra.py`.
- Keep Google addon capability checks in `backend/app/addons/google.py`.
- Do not put provider-specific detection directly into the core mail routes unless a route must call that provider.
- Keep auth/session routes in `backend/app/routers/auth.py`.
- Keep calendar routes in `backend/app/routers/calendar.py`.
- Keep drive routes in `backend/app/routers/drive.py`.
- Keep mail core routes in `backend/app/routers/mail.py`.
- Keep shared mail cache/payload helpers in `backend/app/mail_helpers.py`.
- Keep shared auth/dependency helpers in `backend/app/dependencies.py`.

## Suggested Repo Boundaries

- `backend/app/providers/` for provider adapters
- `backend/app/config.py` for installation mode and provider settings
- `backend/app/capabilities.py` for feature gating
- `frontend/components/` for the shared UI
- `frontend/lib/site-config.ts` for brand and host configuration

## Testing Strategy

Test each installation profile separately:

1. Mail Only
2. Mail + Google Add-on
3. Mail + Native-Zimbra Add-on

For each profile, verify:
- Login
- Mail list and message reading
- Compose and attachments
- Contacts
- Filters
- Mobile and tablet layouts
- Branding

## Known Good Behavior

- Thread expand is message-uid driven.
- Thread detail lookup may fall back from message uid to conversation id on the backend.
- The reader should not re-open by conversation id directly when the row key and conversation id differ.
- If a thread action fails with `no such conversation`, the first thing to verify is that the frontend passed the row message uid and the backend conversation resolver is still active.

## Rollout Plan

1. Finish Edition 1 and treat it as the product base.
2. Keep Google flows as an add-on package.
3. Add the native Zimbra calendar/drive path after the core is stable.
4. Consider SaaS only later, as a separate deployment mode if needed.

## Release Rule

- If a bug affects shared webmail core behavior, rebuild and repack every edition bundle that ships that shared core.
- If a bug is isolated to an add-on, rebuild only the edition bundle that carries that add-on.
- Keep edition bundles versioned separately so the shipped package always matches the code path the customer installed.
