ShootCal
Open web app View demo

HomeHelp › Clients and the client manager

Clients and the client manager

Your ShootCal client list is not a separate database that ShootCal owns. It lives in your own Google Contacts, in a single group (a "label") called "ShootCal Clients." Anyone you add as a client becomes a normal Google contact tagged with that label, so the same roster shows up on your iPhone, iPad, Mac, and the web, and in the Google Contacts app itself, with nothing to sync between devices.

Because clients are just Google contacts, you keep everything: if you delete ShootCal, your clients are still in Google. ShootCal only ever shows you contacts in that one label. (Deleting a client behaves differently on each surface, covered in the FAQ.)

The client manager can also do two helpful things automatically:

1. Scan your calendar for clients. ShootCal looks at the titles of your past and upcoming calendar events (only the titles), uses AI to pull out the real person, couple, or family name from each one (turning "Family pictures for the Smiths" into "The Smiths"), and shows you a reviewable list before anything is saved. You pick which ones are really clients, fix any names, and add the rest.

2. Find their emails. As an optional step, ShootCal can look through your auto-saved Google "Other contacts" (people you have emailed, whom Google quietly remembered) and match a found client name to an email address, so your new client records arrive with a contact email already filled in. This part needs a separate one-time permission, and ShootCal only ever reads those contacts, never writes to them.

Alongside the contact details, ShootCal keeps a count of how many times each client appears on your calendar and when they last booked. That booking history powers the "3rd booking, last booked Mar 2026" hints and the Reports counts.

Where a client actually lives: Google Contacts, one label

There is no clients table in ShootCal's backend. The backend and the native app both treat a single Google Contacts group as the source of truth. That group is named "ShootCal Clients."

A client record is a Google person resource (in the People API). Its identity is the People API resourceName (for example people/c123...). The fields ShootCal reads and writes are names, email addresses, phone numbers, and addresses. The native app additionally reads and writes the contact's biography field to store free-form notes; the web and backend do not read or write notes. Edits use the People API etag for optimistic concurrency: the backend re-fetches the current etag right before an update, because a stale one is rejected.

Membership in the group is what makes a contact a client. The backend checks each person's group memberships against the resolved "ShootCal Clients" group; a contact that is NOT in the group never appears in the client list. That is why both create paths (single and batch) add the new contact to the group and retry once, logging if the group-add fails.

What the app's own database stores (and what it does not)

The backend database holds NO client identities, emails, phones, or addresses. Those are only in Google. What the database does hold is booking history derived from the calendar:

The client list's sort (name / last booked / most bookings) and the type filter are computed from those two sources, keyed by the lowercased client name, with no Google call. A single client's bookings are returned the same database-only way, so the detail view loads instantly.

Native keeps the equivalent counters locally. Each contact carries a session count and a last-session date, but these are local autocomplete hints kept only in the on-disk cache, because Google has no field for them; they rebuild naturally from event saves (saving an event bumps the count and advances the date).

The Google permissions (scopes) involved

The client manager uses two scopes, the same ones on web and native:

Neither scope is "restricted" (no full-mailbox read), so they need consent-screen verification but no paid security audit.

The AI calendar scan, step by step

The web and native apps each have their own entry point (web uses your web session, native uses your Google token), but both call the same shared core, so behavior is identical.

1. Rate limit: one scan per minute per user. The timestamp is stamped only AFTER the scan returns, so a slow or failed scan does not also lock you out. 2. Collect titles: list your calendars (primary plus more, capped at 12 total), read events over the chosen window (default 2 years back / 2 forward; back clamped to 1-5, forward 0-3), skip cancelled events, and collect UNIQUE event titles, capped at 300. Only the title and a first-seen sample date are kept. 3. Extract names: the unique titles are sent to Claude (model claude-haiku-4-5). ONLY titles are sent. The model returns the client name per title (person, couple, or family) or nothing for generic blocks like "Editing" or "Day off," stripping session-type words. When you have session types configured, the model also suggests a type per title (the review dialog lets you correct it). Titles are batched to avoid truncation. 4. Dedup: candidates are dropped if their lowercased name already exists in the ShootCal Clients group or was already seen this scan, so only brand-new names are offered. 5. Review: the candidates (name, suggested type, sample date, source title) are returned for you to approve or edit. If the model was unreachable and produced zero candidates, the app offers a retry instead of saying "no clients found."

Nothing is written to contacts during the scan itself. Approved names are added afterward, by the backend on the web, or by the app's own Contacts write on native.

Email matching from auto-saved "Other contacts"

When you opt into email matching (and have granted the read-only "Other contacts" permission), the scan reads your "Other contacts" once (names and email addresses only). If that read is denied, the permission was not granted and the app prompts you for it.

Matching is pure in-memory name matching (no model call, because the candidate names are already AI-cleaned), against an index of normalized full names and single name-tokens. Confidence tiers:

Couple titles ("X & Y Surname," matched on the raw name so the ampersand survives normalization) try each partner and borrow a shared surname; a couple match is downgraded from exact to high so it is a review suggestion, never a silent write.

For brand-new candidates the matched email is just attached to the review for you to confirm. For EXISTING email-less clients, the heavier repair runs in the background: after the response is returned, ShootCal writes an email onto a client only when the match is exact or high, and only when the email field is currently empty.

Booking-history backfill and the word-boundary match

Every scan also re-links calendar events into booking history, so Reports and the client detail have data without manual entry. ShootCal does one calendar pass (primary plus other calendars, capped at 12, roughly 2 years back and forward) and, for each event, decides which selected client it belongs to: it prefers a STAMPED client (an invisible tag ShootCal saves on the event, which survives a title rename), otherwise the longest title-substring match. The title match treats name boundaries as whole words and skips names shorter than 4 characters, so "Lee" does not mislink "Leesburg HOA" and "Will" does not catch "goodwill."

Type is chosen by precedence: a stamped type on the event, then your per-client override from the review dialog, then a session-type name found in the title, then a couple-name "&" heuristic mapping to your wedding type. Bookings are written idempotently (one per calendar event), so a re-scan never duplicates. Events that already match an accepted booking-page booking on the same day are skipped so nothing double-counts.

For a normal scan this backfill covers the new names; an explicit "re-link all" import re-links EVERY current client, which is what fills in missing last-booked dates across the whole roster.

Native vs web: same model, different write path

Both surfaces use the same Google account and the same "ShootCal Clients" group, and both run the AI scan on the BACKEND (so a single shared key serves everyone and the title-extraction logic stays in one place).

The difference is the contact write. On the web, the backend creates contacts directly: it chunks the new contacts to fit the People API's 200-per-call batch limit and then adds each chunk to the group. On native, the backend scan only returns candidates; the app writes approved contacts itself, using the device's own Google token.

Native additionally keeps a small on-disk cache for instant launch and offline reads, tracks which records were edited offline so a quit-before-sync does not lose them, and binds the cache to the signed-in account email so switching accounts drops a stale rolodex. The web has no such cache; it fetches your clients live and checks whether the group exists to decide whether to show the "how to populate" empty state.

FAQ

Where exactly are my clients stored? Does ShootCal keep a copy of their emails and phone numbers?

In your Google Contacts, in one group called "ShootCal Clients." Each client is a normal Google person resource (identified by a People API resourceName like people/c123...). ShootCal's own backend database does NOT store client names, emails, phones, or addresses; those live only in Google. The database only keeps booking history derived from your calendar (the imported bookings and accepted booking-page requests), which is name plus date plus session type, not contact details.

What permissions (scopes) does the client manager use?

Two. The base contacts read/write scope (https://www.googleapis.com/auth/contacts) powers the whole rolodex: listing the group, creating/updating/deleting contacts, and group membership. The optional email-matching step additionally uses contacts.other.readonly (read-only access to auto-saved Other contacts). The base contacts scope is granted at sign-in; the Other-contacts scope is pending verification, so it is requested the first time you turn on email matching, on the web or on native.

Does the AI scan send my whole calendar, or event details, to the model?

Only event titles. The scan collects unique event titles (capped at 300) over your chosen window and sends just those to Claude (model claude-haiku-4-5). Event descriptions, locations, attendees, and times are not sent. The model returns the extracted client name (or nothing) per title, and an optional suggested session type when you have types configured.

How does it avoid turning generic calendar blocks like "Day off" into clients?

The model is told to return nothing for titles that name no specific client: generic blocks (Editing, Day off, Open slot, Booked, Available), bare places or addresses with no person, or anything it is unsure about, and to never invent a name. Empty results are skipped. Candidates are also deduped against names already in the ShootCal Clients group, so a re-scan only offers brand-new names.

How confident is the email matching, and could it write a wrong address onto a real client?

Matching has three tiers: exact (full normalized name equals a contact), high (token superset or cross-paired first and last name), and low (a single distinctive token of length 4 or more that maps to exactly one email, or a multi-token overlap resolving to exactly one email). Only an exact match is ever auto-written onto an EXISTING client, and only when that client currently has no email. High matches, low matches, and any couple-derived match (which is downgraded from exact to high) are shown as review suggestions for new candidates, not silently written. A single token shorter than 4 characters, or one that resolves to more than one email, is not matched at all, so ambiguous single first names are dropped instead of guessed.

Are "Other contacts" ever modified?

No. They are read with the read-only scope contacts.other.readonly, and only names and email addresses. The only contact writes ShootCal makes are to your own ShootCal Clients group (creating and updating client records). The email-fill that does happen writes onto your client contact in that group, using the matched email value, never onto the Other contact.

If two devices edit the same client, or I scan twice, what prevents conflicts and duplicates?

Updates go through the People API etag (optimistic concurrency); the backend re-fetches the current etag right before updating and retries once on a stale-etag failure. The booking backfill is idempotent: there is at most one imported booking per calendar event, and same-day matches against accepted booking-page bookings are skipped so they do not double-count. Scan candidates are deduped by lowercased name against the existing group and within the scan.

Why does the heavy part of a scan run after the response, and what is that exactly?

On a large roster, re-linking every current client's calendar sessions plus backfilling emails inline would time the interactive scan out. So ShootCal returns the scan response to you first, then runs the heavier repair in the background: a full-roster calendar pass into booking history and (if email matching is on) filling empty emails on existing clients from exact and high Other-contacts matches. The scan result notes that the repair is queued so the app can say it is updating in the background.

What happens to my clients if I stop using ShootCal, and what does deleting a client do?

They remain in your Google Contacts under the ShootCal Clients label, because that is where they always lived. Deleting a client behaves differently per surface. On native, delete ALWAYS just unlinks the person from the ShootCal Clients label; it never destroys the underlying Google contact, so the person stays in your account. On the web, the Delete button removes the contact from Google Contacts entirely; the web confirm dialog warns you that it does so.

Native shows a "3rd booking, last booked Mar 2026" hint. Is that synced?

Not through Google. Google has no field for session count or last-booked date, so native keeps the session count and last-booked date only in its local on-disk cache and treats them as per-device autocomplete hints that rebuild from event saves (saving an event increments the count and advances the date). The web computes the equivalent stats server-side from the imported bookings and accepted bookings instead.

← Back to Help Center