# Match Members to Slack via a cached whole-workspace directory We resolve Slack **Avatars** by sweeping the entire workspace once with `users.list` and caching an `email → {imageUrl, slackId}` directory in SQLite, rather than calling `users.lookupByEmail` per Member. A **Match** is then an in-memory lookup, and an unmatched Member is simply one whose email isn't in the directory. ## Considered Options - **`users.lookupByEmail` per Member** — only fetches who you view, but makes N calls per uncached Project view and is more exposed to per-method rate limits. - **`users.list` directory sweep (chosen)** — one paginated sweep covers everyone; repeated Project views cost zero Slack calls; matching is in-memory. ## Consequences - Cache is a directory snapshot + a single `refreshed_at`, with a **30-day TTL** and a manual **Refresh** (`POST /api/slack/refresh`) — Avatars change rarely, so staleness is cheap and the button covers the exceptions. - If the sweep fails, we still return Members (unmatched) and the UI degrades to initials Avatars + a warning — a Slack outage never hides the roster. - Deleted/bot Slack users are filtered out of the directory.