Files
webmail-client/backend/pyproject.toml
T
lukasandClaude Opus 5.5 fba3c947bf Attachment previews, forwarded attachments, row dividers
Attachments in the reading pane are tiles with a preview: images and a PDF's
first page, rendered on the server (Pillow, pypdfium2) as WebP of at most
480 px, cached beside the blob store and in the browser. Tiles are all one
size — at most 230 px wide, narrower so three still fit in a row — and frame
each preview by its shape: close to the tile's it fills it, very tall / wide /
square ones show whole over a blurred copy, small ones are not enlarged, PDFs
show the top of the page. The name bar (checkbox, type, name, size, download)
slides in on hover and adapts to the tile's width; files without a preview
get a large type icon. Previews of opened conversations are kept offline.

Forward now carries the original's attachments: new compose attachments
sharing the stored blobs (POST /messages/{id}/forward-attachments).

Mailbox display / Settings → Appearance: "Show a dividing line between
rows" (off by default, per device), with a new switch. Settings drop the
"On a phone…" note, and "Language" reads "Spellchecking language".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:15:17 +02:00

92 lines
2.8 KiB
TOML

[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "webmail-backend"
version = "0.1.0"
description = "Webmail client backend (sync-first, see webmail-client-spec.md)"
requires-python = "==3.11.*"
# Pinned per SPEC §3. Do not swap libraries or add deps without a `# DEPENDENCY:` comment.
dependencies = [
"fastapi~=0.115",
"uvicorn[standard]~=0.30",
"sqlalchemy[asyncio]~=2.0",
"asyncpg~=0.29",
"alembic~=1.13",
"pydantic~=2.8",
"pydantic-settings~=2.4",
"arq~=0.26",
"redis~=5.0",
"aioimaplib~=1.1",
"imapclient~=3.0",
"aiosmtplib~=3.0",
"argon2-cffi~=23.1",
"pyotp~=2.9",
"dnspython~=2.6",
"httpx~=0.27",
"nh3~=0.2",
"cryptography~=43.0",
"mail-parser~=4.0",
"meilisearch-python-sdk==2.7.0", # last release that fits the pinned Meilisearch v1.8
"python-multipart~=0.0.9",
# DEPENDENCY: SPEC §6.2 mandates accounts/presets.yaml (config, not code); the
# stdlib has no YAML parser, so PyYAML is required to load it.
"pyyaml~=6.0",
# DEPENDENCY: autoconfig/ISPDB return XML from untrusted servers; defusedxml
# blocks XXE/billion-laughs that stdlib ElementTree is vulnerable to (§10 hostile input).
"defusedxml~=0.7",
# DEPENDENCY: thumbnails of attachments in the reading pane — Pillow scales
# images; pypdfium2 renders a PDF's first page (a self-contained wheel, no
# system libraries, so the slim runtime image needs nothing extra).
"pillow~=11.0",
"pypdfium2~=4.30",
]
[project.optional-dependencies]
dev = [
"ruff~=0.6",
"mypy~=1.11",
"pytest~=8.3",
"pytest-asyncio~=0.24",
"respx~=0.21",
"aiosqlite~=0.20",
]
[tool.setuptools.packages.find]
where = ["."]
include = ["app*", "workers*"]
# Non-Python files that a *non-editable* install (the Docker image) must carry:
# the account presets config (SPEC §6.2), the shared Alembic env used by the
# per-plugin migration runner (app/plugins/migrate.py) and the logo embedded in
# system mail (app/identity/mailer.py). Without these, a `pip install ./backend`
# produces a package that fails at runtime.
[tool.setuptools.package-data]
app = ["accounts/presets.yaml", "plugins/alembic/*", "identity/assets/*.png"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "ASYNC", "C4", "SIM"]
ignore = ["B008"] # FastAPI Depends() in defaults is idiomatic
[tool.mypy]
python_version = "3.11"
strict = true
plugins = ["pydantic.mypy"]
mypy_path = "."
explicit_package_bases = true
namespace_packages = true
[[tool.mypy.overrides]]
module = ["aioimaplib.*", "mailparser.*", "imapclient.*", "arq.*", "yaml", "defusedxml.*"]
ignore_missing_imports = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
pythonpath = ["."]