Summary
The web feed renders rich link preview cards (title, description, thumbnail, host) — visible on the public landing page. The app renders the bare URL.
Live verification (2026-09-15)
GET /api/link-metadata?url=https://jmap.io/ -> 200
{"link":{"url":"...","platform":"other","metadata":{"type":"link","title":"...","description":"...","thumbnail":"..."}}}
GET /api/messages/{id}/metadata -> 200 {"links":[{"url":"...","metadata":{type,title,ogType,thumbnail,description}}]}
API
linkMetadata arrives inline on each message in GET /api/messages — often no extra call is needed.
GET /api/messages/{id}/metadata — stored metadata only (lightweight).
POST /api/messages/{id}/metadata — fetch and persist metadata for a message's links.
GET /api/link-metadata?url= — ad-hoc unfurl, e.g. a compose-time preview.
Respect the user preference
GET /api/user returns showPreviews. When it is off, do not render preview cards (see the Settings epic).
Acceptance criteria
Summary
The web feed renders rich link preview cards (title, description, thumbnail, host) — visible on the public landing page. The app renders the bare URL.
Live verification (2026-09-15)
API
linkMetadataarrives inline on each message inGET /api/messages— often no extra call is needed.GET /api/messages/{id}/metadata— stored metadata only (lightweight).POST /api/messages/{id}/metadata— fetch and persist metadata for a message's links.GET /api/link-metadata?url=— ad-hoc unfurl, e.g. a compose-time preview.Respect the user preference
GET /api/userreturnsshowPreviews. When it is off, do not render preview cards (see the Settings epic).Acceptance criteria
Models/Message.csgainsLinkMetadata.linkMetadata, honoringshowPreviews.GET /api/link-metadatawhen the draft contains a URL.