Files
webmail-client/docs/open-questions.md
T
lukasandClaude Opus 4.8 a5ec0d21cb Initial commit: webmail client (M0/M1)
Three-pane webmail client.
- Backend: FastAPI + async SQLAlchemy + arq worker (Postgres/Redis), IMAP/SMTP
  sync, threading, search, drafts/outbox with undo, attachments, 2FA.
- Frontend: React 18 + Vite + TypeScript + TanStack Query + Tailwind; three-pane
  UI with rich-text composer, light/dark themes, resizable panels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 23:20:34 +02:00

5.4 KiB

Open Questions

Per the setup rules, deviations and unresolved decisions are recorded here rather than guessed silently.

OQ-1 — Meilisearch container healthcheck reports unhealthy (IPv6 localhost)

Status: resolved — approved deviation applied. The healthcheck URL in docker/docker-compose.yml was changed from localhost to 127.0.0.1; the container now reports healthy.

Symptom: docker compose ps shows meilisearch as unhealthy, even though the service is fully up and reachable from the host (curl http://127.0.0.1:7700/health returns {"status":"available"}).

Root cause: the spec's healthcheck is wget -qO- http://localhost:7700/health | grep -q available. Inside the getmeili/meilisearch:v1.8 container, /etc/hosts maps localhost to both 127.0.0.1 and ::1, and busybox wget tries the IPv6 ::1 first. Meilisearch listens IPv4-only on 0.0.0.0:7700 (MEILI_HTTP_ADDR=0.0.0.0:7700), so the ::1 connection is refused and the healthcheck fails. wget http://127.0.0.1:7700/health from inside the container succeeds.

Impact: cosmetic for direct use (the service works), but real for automation: docker compose up --wait (used by the reset-db Make target) and the "all must be healthy" verification step will hang/fail on this host.

Proposed minimal fix (one word): change the healthcheck URL from localhost to 127.0.0.1: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:7700/health | grep -q available"]. This is a deviation from the frozen setup doc, so it is left for explicit approval.

OQ-5 — Relative BLOB_ROOT breaks across processes with different CWDs

Status: resolved in dev by using an absolute path.

BLOB_ROOT=./var/blobs (relative) resolves against each process's working directory. Per the dev-setup doc the API runs from the repo root (uvicorn --app-dir backend) while the worker runs "from backend/", so blobs written by the worker land in backend/var/blobs but the API reads ./var/blobs at the repo root — a different, empty directory. This only surfaced once the API began reading blobs (M1 re-sanitize-on-render / attachments); before that the API only served sanitized HTML from Postgres.

Fix: .env.dev now sets an absolute BLOB_ROOT. Additionally GET /messages/{id}/html falls back to the stored (possibly stale) sanitized HTML if a blob is unreadable, so a path/permission issue degrades gracefully instead of 500-ing. .env.example keeps the relative default with a note; production should always use an absolute path or run both processes from the same CWD.

OQ-4 — Duplicate Message-ID / bulk-sender over-merging (threading)

Status: resolved with two refinements (both consistent with §7.1's stated intent).

Symptom (found while testing): home.pl's welcome mailer reuses one static Message-ID (and Date) for every recipient. Two welcome mails — one per test account, 104 days apart, byte-identical — collapsed into a single thread.

Cause & fixes:

  1. Duplicate Message-ID (§7.1, was only half-implemented). The indexer now synthesizes a non-authoritative id when a Message-ID is already present in a different account (a separate delivery). A same-account duplicate keeps the real id, so Gmail-style INBOX + All Mail copies of one message still thread together. Implemented in app/indexer/indexer.py::_resolve_message_id; new index ix_messages_user_msgid (migration 0002).
  2. Subject fallback (§7.3 refinement). The literal rule ("same subject + within 7 days + shares ≥1 participant") over-merges bulk mail, because the shared participant can be just the common sender. Subject joining now keys on the incoming message's recipients (To/Cc) overlapping the thread — a genuine reply is always addressed to someone in the thread, a bulk blast to another account is not. app/threading_/threader.py::_find_by_subject.

Both are minor tightenings of the spec's anti-over-merge intent, not behavior the spec forbids. Covered by tests/indexer/test_duplicate_msgid.py and updated threading vectors.

Status: resolved with a documented dev fallback.

SPEC §6.1/§10.3 mandate the __Host-session cookie prefix, which the browser only accepts when the cookie is Secure and sent over HTTPS. The dev setup (dev-environment-setup.md) serves the API over plain HTTP (uvicorn :8000 behind the Vite HTTP proxy), so a __Host-/Secure cookie would be silently dropped and login could never work in dev.

Decision: cookie naming/flags are derived per-request from the scheme (app/identity/cookies.py): over HTTPS we emit the spec-mandated __Host-session + Secure; over plain HTTP (dev only) we fall back to session without Secure. Production (behind TLS) is fully spec-compliant. SameSite=Lax, HttpOnly, Path=/ always. The CSRF cookie follows the same scheme rule but is readable by JS (double-submit, SPEC §10.3).

OQ-2 — Repository was not a git repo (resolved)

Status: resolved.

The setup doc's conda activate.d hook locates .env.dev via git rev-parse --show-toplevel, and the overall workflow (commit-per-increment, per-plugin alembic branches) assumes git. The working directory was not a git repo, so .env.dev did not auto-load on conda activate. Ran git init (branch main); the hook now loads the env correctly. No spec deviation — the doc already assumes git.