Galleries Sharing, Download Basket, and Owner Notifications
This document is the active source of truth for the next gallery delivery work after the owner library/session decoupling refactor.
It covers four closely related concerns that must be implemented as one coherent product surface:
- share a gallery with specific people,
- keep owner preview/live delivery behavior coherent,
- let recipients select multiple images for bulk download,
- notify the photographer when someone downloads one or more photos.
Related docs
- SLIDESHOW_EDITOR_REDESIGN.md — slideshow editor, cover modes, timeline UX, export quality, render queue (
gallery-slideshow), and published playback on owner / public / client portal surfaces. Use it when delivery work touches published slideshow or shared gallery viewing—not only ZIP downloads. - GALLERIES_LIBRARY_PLAN.md — owner gallery library refactor, session decoupling, and
/galleriesnavigation; sharing builds on that workspace model.
Progress
Slice 1 - download event tracking: in progress- plan documented
- migration for
gallery_download_eventsstarted - backend tracking service started
- existing owner / portal / public download endpoints being instrumented
- owner gallery download stats endpoint added as the first reporting surface
Slice 2 - basket download API: in progress- owner / portal / public POST
download-urlendpoints now acceptassetIds[]for ZIP basket generation - public / portal / owner preview viewers now expose a dedicated local download basket, separate from favorites
- owner / portal / public POST
Slice 3 - share recipients: in progressgallery_sharesmigration added- owner share CRUD routes added
- share token can now unlock a gallery before publication through the same slug URL
- first owner UI for direct share links is now wired in the gallery editor
- share permissions can now be adjusted inline from the editor links tab
- direct shares now read as authorized recipients when created from name + email
- send-email flow from the gallery editor is now being wired with a review modal before send
- direct shares now distinguish
clientvsguest - each listed recipient now carries download activity counters
- public gallery access now supports explicit modes:
open,password, orname + email - the public lock screen now uses one clear entry mode at a time instead of stacking password and request flows
- owner sharing surfaces now focus on
direct sharesonly; manual access-request approval is removed from the main UI and API flow
Slice 4 - owner activity panel: in progress- owner gallery download stats are visible in the editor delivery tab
- recent download activity feed is now exposed and rendered in the editor
Immediate Next Slice
The next delivery slice after recipient typing is guest-specific visibility:
- allow assets to stay visible to clients while hidden from guests,
- expose that control in the gallery editor,
- enforce it on public/direct-share viewers based on
recipientType.
Why This Needs A Dedicated Plan
The current gallery domain already contains several adjacent concepts:
- public gallery access by slug,
- owner preview access,
- client portal gallery access,
- favorites / gallery selection,
- section/gallery ZIP downloads.
Those flows work, but they are not yet organized around a clear delivery model for real recipients.
The main risks if we implement the next features ad hoc are:
- overloading
selectionto mean both favorites and download intent, - mixing owner preview and public sharing as separate products,
- creating notifications that cannot identify who downloaded what,
- adding share links without a permissions model.
Product Principle
There should be one gallery experience, with different access modes:
owner preview: same external gallery URL, available to the owner even before publish,public live: same URL becomes public when the gallery is published and public access is enabled,direct recipient share: scoped access for named recipients who are not necessarily portal clients,client portal access: authenticated access for linked portal clients.
The UI should not present four unrelated systems. The domain should express one delivery surface with explicit access channels.
Current State
Already implemented:
- owner library
/galleries, - session-linked and standalone galleries,
- public slug URL,
- owner preview via the same external gallery URL,
- portal visibility toggle,
- public password,
selectionon public/client/owner preview,- gallery and section ZIP download endpoints.
Current gaps:
- no gallery-specific sharing model for inviting named external recipients,
- no owner notification when a recipient downloads assets,
- no basket selection dedicated to download,
- no audit trail of downloads by actor, scope, and assets.
Domain Model To Add
1. Gallery Shares
Create a dedicated gallery share model instead of reusing generic organization resource shares.
Recommended table: gallery_shares
Fields:
idgallery_idowner_idlabelemailfirst_namelast_nameaccess_modeLINKEMAIL_LINKPORTAL_LINKED
token_hashexpires_atrevoked_atallow_web_downloadallow_full_downloadallow_favoritesallow_submit_selectionallow_download_basketcreated_atupdated_at
Why a dedicated table:
- gallery-specific permissions differ from org resource sharing,
- public gallery access is anonymous/tokenized, not organization membership,
- we need per-recipient audit and notification metadata.
2. Gallery Share Sessions / Access Tokens
For public recipients, do not rely only on the public gallery password token. Introduce scoped access tokens for gallery shares so the app can identify:
- who accessed the gallery,
- which share was used,
- whether the actor is portal, owner, or external recipient.
Recommended table: gallery_share_access_tokens
Fields:
idgallery_share_idtoken_hashissued_atexpires_atlast_used_atrevoked_atsource_labelsource_typeOWNER_PREVIEWPUBLICPORTALSHARE
3. Download Events
Add an explicit audit table for download activity.
Recommended table: gallery_download_events
Fields:
idgallery_idgallery_share_idnullableselection_idnullableactor_typeOWNERPORTAL_CLIENTPUBLIC_RECIPIENT
actor_labeldownload_scopeASSETSECTIONGALLERYBASKET
download_kindWEBFULL
asset_countsection_idnullableasset_idsjsonbcreated_at
This table is the basis for:
- owner notifications,
- future reporting,
- abuse/debug visibility.
4. Download Basket
Do not reuse selection.assetIds for bulk download.
Reason:
- current
selectionis clearly tied to favorites/client curation, - download basket is operational and temporary,
- forcing them into one field creates confusing UX and brittle rules.
Recommended approach:
- basket state is client-side first,
- persisted optionally only if needed later,
- server receives
assetIds[]when generating a basket ZIP.
That means the first implementation does not require a new persistent basket table.
UX Model
Gallery Sharing Workspace
Add a dedicated Sharing workspace inside gallery settings/editor, focused on real delivery:
Sections:
-
Access channels- Client portal
- Public link
- Direct shares
-
Live gallery URL- one URL only
- preview/live state
-
Recipients- recipient list
- add recipient modal
- resend / revoke / copy share link
-
Download permissions- web/full
- basket allowed
- favorites allowed
-
Activity- recent downloads
- recent access
Recipient Flow
Recipients should be able to:
- open the gallery from a secure share link,
- browse normally,
- select images into a
download basket, - trigger
Download selected, - optionally use section/gallery ZIP if permissions allow.
Owner Notifications
Owner should receive a notification when:
- a recipient downloads one asset,
- a recipient downloads a basket,
- a recipient downloads a section,
- a recipient downloads the full gallery.
Notification examples:
Emily Carter downloaded 6 photos from Wedding galleryPortal client downloaded the Reception sectionPublic recipient downloaded the full gallery in web resolution
Backend Slice Plan
Slice 1: Download Event Tracking
Goal:
- no new recipient model yet,
- instrument existing asset/section/gallery downloads,
- notify owner.
Work:
- add
gallery_download_events, - record events from:
- owner preview download endpoints,
- client portal download endpoints,
- public gallery download endpoints,
- create owner notifications through
NotificationsService.
This is the lowest-risk first slice because it builds observability immediately.
Slice 2: Basket Download API
Goal:
- allow selecting arbitrary visible assets for ZIP download.
API additions:
- owner preview:
POST /sessions/:sessionId/galleries/:galleryId/download-urlbody:{ kind, assetIds? }
- portal:
POST /client-accounts/me/galleries/:galleryId/download-urlbody:{ kind, assetIds? }
- public/share:
POST /public-galleries/:slug/download-urlbody:{ kind, assetIds?, accessToken? }
Important:
- keep existing GET section/gallery ZIP routes for compatibility,
- add POST for basket ZIP generation,
- validate that every requested asset belongs to the gallery and is visible in the caller’s scope.
Slice 3: Share Recipients
Goal:
- create named share recipients with permissions.
API additions:
GET /galleries/:galleryId/sharesPOST /galleries/:galleryId/sharesPATCH /galleries/:galleryId/shares/:shareIdDELETE /galleries/:galleryId/shares/:shareIdPOST /galleries/:galleryId/shares/:shareId/send
Public/share access:
- add share token resolver that can open the same gallery page in recipient mode,
- same gallery UI, different actor context.
Slice 4: Activity Panel
Goal:
- expose download/access history in the owner gallery editor.
API:
GET /galleries/:galleryId/activity
Frontend Slice Plan
Slice 1: Download Basket On Gallery Page
Public and portal gallery pages get:
Select for downloadmode,- per-asset checkbox/selection affordance,
- sticky action bar:
- selected count,
- clear selection,
- download selected.
This must remain distinct from Favorites only.
Slice 2: Sharing Workspace In Editor
Gallery editor gets:
- recipient list,
- add recipient modal,
- delivery permissions per recipient,
- activity summary.
Slice 3: Notifications Surface
No dedicated screen required initially. Use existing in-app notifications and, later, optional email notifications.
Notification Design
Add new notification types:
GALLERY_ASSETS_DOWNLOADEDGALLERY_SECTION_DOWNLOADEDGALLERY_BASKET_DOWNLOADEDGALLERY_FULL_DOWNLOADED
Metadata should include:
galleryIdgalleryTitleshareIdactorTypeactorLabeldownloadKindassetCountsectionIdwhen relevant
Implementation Rules
- Do not overload existing
selectionsemantics. - Do not create a second gallery viewer for shares.
- Do not make preview and live separate URLs again.
- Prefer explicit audit events over inferred behavior.
- Keep backward compatibility for existing public/client download endpoints.
- Use one notification creation path through
NotificationsService.
Recommended Execution Order
- download event tracking + owner notifications
- POST basket ZIP endpoints
- public/client/preview download basket UI
- gallery share recipients model + management UI
- activity panel in gallery editor
Open Questions
- Should external recipients also be able to submit favorites/selection, or only download?
- Should recipient share links expire by default?
- Do we want email notifications in addition to in-app notifications for owner download events?
- Do we want recipient identity to be mandatory, or allow anonymous named links?
Current recommendation:
- allow named recipients,
- default to expiring links,
- in-app notifications first,
- email notifications later,
- keep anonymous public link as a separate, coarser access channel.