Skip to content
Browse documentation
Decided

Contacts and invitations

The Contacts tab: finding people, sending and answering invitations, the contact card, favourites, nicknames, relations, removing and blocking.

Updated: 29 October 2026

3.1 Contacts tab

Top bar: the profile switcher on the left; the bell with its unread count (Notifications) and Find (Find people, the primary action) on the right. Title “Contacts”. A search field under the title filters the local cache (names, nicknames, e-mails, phones) as you type. Mocks: mobile/design/redesign/redesign-round-2.html and redesign-round-5.html (A109).

Circle chips under the search field filter everything below them: All with the number of contacts, then one chip per circle with its dot, name and count, scrolling sideways. One chip is selected at a time; the choice is kept per profile.

Sections, top to bottom; an empty section is hidden:

  1. Needs you: received invitations that are not ignored. Rows show avatar, default name and “wants to connect · 2 days”, with Ignore (secondary) and Answer (primary). Answer opens 3.1.3; Ignore hides the request (A103, below). It is the first thing on the screen whenever something waits for an answer.
  2. Changed this week, with the count on the right: connections whose card changed in the last seven days, newest first, drawn as changed rows (accent bar and tint): avatar, name, what changed in words and when (“New mobile number · today”, “New home address · 3 days ago”), and the circle label. “Changed” is worked out on the device from connections_previous and snapshot_at (Offline and sync), by the same comparison the contact card uses to mark changed rows; the line names the first changed category. Tapping opens the card with those rows marked, as a contact_updated tap does.
  3. Favourites: pinned contacts in the user’s order, shown only when there are any. Order stored in favorite_index, which stays contiguous: a new favourite is appended and unstarring one closes the gap it leaves. Reordering follows the platform (A68): long-press drag on iOS, Move up / Move down actions on Android; both are also accessibility actions. Reordering is hidden while a search or a circle chip is filtering, because a step would swap a visible row with a hidden one.
  4. Everyone, with “A–Z” on the right: alphabetical by nickname-or-default-name with section letters and an alphabet index rail (as in Saymail’s People tab). Rows: avatar, name with a small gold star when favourited, one detail line, and the circle label (dot and name in colour) at the end.
  5. Requests: collapsed to one line, “Requests · 1 sent · 1 ignored” with Show. Expanded it lists sent invitations (“waiting · sent 5 days ago”, tap → 3.1.4) and ignored received ones (with Answer).

Ignore (A103) hides a received invitation from Needs you, from the bell count and from the Needs you section of Notifications; it stays answerable under Requests, and Answer there works as before. Ignoring sets no deadline of its own and tells the sender nothing: on their side the invitation still says “waiting”. The invitation’s own 30-day expiry still applies. Ignore is ignore_invitation(id), which only the recipient may call and which stamps invitations.ignored_at; un-ignoring is answering.

While offline the list shows the cache under a banner “Offline. Showing what was saved at 9:12.”; Find is dimmed with “needs a connection”. While the first read is in flight the list shows skeleton rows. A circle chip with nobody in it shows “Nobody in Book club yet. The filter hides all 48 of your contacts.” with Show everyone and “Move people into Book club”.

The detail line follows v2’s rule: take the preferred kind from Settings (e-mail or phone); show it if a verified one is shared; otherwise any verified detail of the other kind; otherwise the preferred kind unverified; otherwise the first available detail; otherwise nothing.

Avatars without a photo show initials on a colour derived from the profile id with one shared hash function (v2 had six copies producing different colours per screen; v3 has one, pinned by a golden vector).

Swipe actions on a contact row: favourite/unfavourite (leading), move circle and remove (trailing). One swipe direction cannot carry two verbs, so the trailing swipe opens a small menu holding both; on a tablet the same verbs are trailing buttons rather than a swipe, because a drag there means reassignment (A63). Empty state: “No contacts yet. Find people you know.” with the Find button, shown only once the local cache has answered — never while the first read is still in flight.

Search folds diacritics, so “jose” finds “José”, and matches a phone number by its digits however it is spelled, so “06 12” finds the number shown as “+31 6 12…”. Sorting and the section letters use the viewer’s language collation, and a name that does not start with a letter sits under ”#”.

3.1.2 Find people

Two segments (A106), Search and In your phone · 12 (the count once the check has run). Mock: mobile/design/redesign/redesign-round-3.html, variant 2.

A search field with a hint “E-mail address, phone number or name”, and under it the line “An e-mail address, a phone number, or whole words of a name.” Behaviour:

  • E-mail (contains @): normalised, hashed on device, exact match. A person is findable by the address they signed in with as soon as that row is searchable: onboarding seeds it searchable and the server already counts it as verified (ruling A70). Every other address needs its code entered first.
  • Phone: parsed with the device region, E.164, hashed, exact match.
  • Name: lower-cased whole words; each word ≥ 3 characters; matches profiles whose searchable names contain all words.

Results (max 20) come under “3 found” and show avatar, default name and “Found by e-mail / phone / name”, never the matched value itself, with Invite (primary). Profiles already connected show “Connected · Work” with Open; pending ones show “Invitation sent · 5 days ago” with a dimmed Invited, or “Invited you” with Answer. Nothing else about the person is shown before connecting. Under the results a fill box with a lock: “Nobody sees anything about a person until you are connected. You only find people who made that detail findable.” Rate limit: 60 searches per hour; hitting it dims the field and shows “Try again in a few minutes. You searched 60 times in the last hour, the most Incico allows, so nobody can be found by guessing.” with “Check your phone contacts instead”.

Finding one person and checking a whole address book are different questions, and they use different functions. This screen calls search_profiles: at most 20 tokens, 60 calls an hour, and the matched value is never echoed back, because a searcher already knows what they typed. The cross-reference of Connected services calls cross_reference instead: up to 200 hashed e-mail and phone tokens a call, 30 calls an hour, no name tokens at all, and every hit carries the token that matched so the device can name the contact it came from. Both apply the same four exclusions — your own profiles, people who opted out, blocks in either direction, accounts that have left — and both return a default name and an avatar path and nothing more (A30).

In your phone

The cross-reference of Connected services §3.3.1.2, run over the phone’s own address book from here, so people look for friends where they expect to. It needs the contacts permission, asked after the in-app explanation (principle 9); a person who has connected the phone’s contacts as a service has granted it already. The segment says “12 people in your phone contacts use Incico. Checked by a scrambled code of their number and e-mail; their details never leave your phone.” Then Not connected yet with Invite all (asks for the circle once) and rows “In your phone · matched by phone” with Invite; and Already connected · 9, collapsed, dimmed rows with their circle label. Invite opens 3.1.2.1 for that person. Results persist in the local cache as the service’s cross-reference does.

Below the search results: “Not on Incico yet? Share an invite link” (deferred to 3.1; the row is hidden until the feature ships).

3.1.2.1 Send invitation

Header: avatar, “Invite Sanne van Dijk” and the line “Nothing of you is visible until the invitation is accepted.” Then:

  • Put Sanne in (required, marked so): radio list of the user’s circles, each with dot, name and the line of what that circle sees (“Sees your mobile, personal e-mail, city and birthday without year”), generated from the details; the selected row is tinted.
  • Relation (optional, shown for every circle, not only Family as in v2): chips None / Parent / Child / Partner / Sibling. Choosing one shows “They will be asked to confirm this relation.”
  • Message (optional, 280 characters, counter “0 / 280”).
  • Send invitation with the send icon, full width at the bottom, disabled until a circle is chosen.

On send: send_invitation runs; the screen is replaced by 3.1.2.2 Invitation sent (check icon, recipient card, chosen circle, relation, date, button “Back to contacts” or back to the cross-reference screen when it came from there, matching v2’s returnTo).

3.1.3 Received invitation

Full page (round 3, variant 1). Top bar: ”‹ Contacts” and the overflow (Block). The requester’s avatar and default name with “wants to connect · 2 days ago” (the avatar is a photo only when the requester’s avatar photo is shared with the circle they chose for you: the only detail visible before accepting, readable through the storage policy for exactly as long as the invitation is pending), the message as a speech bubble, and, when a relation is claimed, a warning box: “Anna says she is your sister. Family details are often shared more widely; only accept if you know Anna.” Then:

  • Put Anna in (required): the same radio list with “Sees …” lines as Send invitation.
  • When a relation was claimed, a switch “Anna is my sister” with “Off: you connect without the relation” (default off; accepting without confirming stores relation = none on the recipient’s side).
  • Footer: Decline (secondary) and Accept into Family (primary, naming the chosen circle).

The avatar and the name do not come from profiles, which stays closed until the two are connected: the screen reads them with invitation_parties, a batched call that returns the default name and the avatar path of both parties of every invitation the caller is a party to, and nothing for any other id.

Accept calls accept_invitation; both cards appear immediately (the snapshots are computed in the same transaction) and the screen is replaced by the new contact card. Decline removes the request silently for the requester (they see “declined” only as the request disappearing from Sent).

3.1.4 Sent invitation

Top bar: ”‹ Contacts” and the overflow. The recipient’s initials avatar and name with “Waiting · sent 5 days ago”. When Pieter accepts: the circle label with the line of what that circle will see, and Change, which picks another circle while the invitation is pending (set_invitation_circle(id, circle), sender only, pending only); the relation; the message. Reminder: Send reminder, with its reason under it while it cannot be sent (“From 3 November, a week after sending · one reminder”). Footer: Cancel invitation in danger, which asks first.

The recipient’s name and avatar come from the same invitation_parties call the received list uses; a recipient who has since deleted their account comes back with a null name and is drawn as the placeholder a removed contact gets. The recipient’s avatar is never shown while the invitation is pending: they have shared nothing yet, so the row shows initials.

The reminder is the server’s decision, not the screen’s: remind_invitation allows exactly one, and only seven days or more after the invitation was sent, and records it in invitations.reminded_at. It inserts a second invitation_received notification for the recipient, so the reminder arrives as the invitation did. Before the week is up, or after a reminder has been sent, the button is disabled and the reason is shown; a client that calls anyway is answered with rate_limited.

3.1.1 Contact card

One page (round 2, variant 1). Top bar: ”‹ Contacts” and the overflow. Centred: a 72 dp avatar (or initials), the display name (nickname if set, otherwise default name), and under it the default name and relation (“Ellen de Vries · Parent”). Under that the circle label, Move (opens Move to circle) and the favourite star (tap to toggle). Then four quick actions, each an icon over its word: Call, Message, E-mail, Route; one that has nothing to act on (no phone shared) is dimmed.

Body, only sections that have content (v1 rule), rendered from the snapshot:

  • Names (other names shared with you, with type labels)
  • Phone numbers: tap to call, long-press to copy / message / WhatsApp via the OS share sheet
  • E-mail addresses: tap to compose
  • Addresses: formatted per country; tap opens Maps; per-field shared addresses show only the shared fields
  • Special dates: “14 March” or “14 March 1991”; tap adds a yearly calendar reminder (A93). The app hands the calendar app a pre-filled all-day event that repeats every year (RRULE:FREQ=YEARLY), titled “Birthday: Anna”, “Anniversary: Anna”, or the owner’s own word for a custom type (“Name day: Anna”), starting on the next occurrence of the date (29 February waits for the next leap year). It asks for no calendar permission and reads nothing back: the calendar app shows the event and the person saves it or not. A phone with no calendar app says “No calendar app found.”
  • Photos: horizontal strip, tap to view full screen

A changed row (A94) says so in its label: “Mobile · new today”. Footer: an info box “Mum sees 7 of your details, as Family” with Preview (the preview_circle of that circle), then “Last updated 3 days ago”. Overflow menu: Edit nickname, Move to circle, Remove contact, Block, Report (opens a mail to support with the profile id).

Nickname editing is inline (v1 behaviour): tap the pencil, edit, Save or Cancel; Cancel reverts to the last saved value.

Remove contact: confirm sheet “Anna will no longer see your details and you will no longer see hers.” Removes both connection rows silently: neither side is notified (A102), the card simply leaves the other person’s list at their next refresh; connected services move the entry to the delete bucket (or leave it if allow_delete is off).

Block: confirm sheet; removes the connection and prevents future search and invitations. Blocked people are listed in Settings → Privacy → Blocked, where they can be unblocked.

3.1.5 Notifications

Needs you first (round 4, variant 2; see Notifications). Needs you lists invitations still to answer, each with Answer and Ignore. Updates lists the rest, newest first: contact updated, saying what changed and whether it reached the address book (“Bram · new mobile number”, “Today 09:12 · written to Google” or “waits for sync”; tap → card with the changed rows highlighted for 3 seconds), invitation accepted (“Joris accepted”, “Yesterday · in Friends”; tap → card), circle changed (“Anna changed what you can see”), system. Unread rows are bold with a dot; the avatar carries a small badge for the kind. “Mark all read”. The unread count drives the bell badge and the app icon badge; ignored invitations do not count. Nobody is told when a connection ends (A102).

Changed rows (A94). “Changed” is worked out on the device: the delta fetch keeps the snapshot each connection had before its last change (connections_previous, local only, see Offline and sync), and the card compares the two by detail id. A row that is new, or whose content differs (a new number, a newly shared city or year), gets an accent bar at its start and a faint accent tint that fades in and out; with reduce motion only the static bar shows, for the same three seconds. While marked, the row’s accessibility state says “Recently changed”. A row that was removed has nothing to mark. Only a contact_updated tap marks anything - from the push, the in-app list or the /n/{id} link; opening the card any other way shows no marks, and once the three seconds are over the card does not mark them again.

Rules

  • One pending invitation between two profiles at a time, in either direction; inviting someone who already invited you opens their invitation instead.
  • A user cannot invite their own profiles; a managed profile can be connected to the account’s other profiles (parent ↔ child) normally, through the same flow.
  • Invitations expire after 30 days and disappear from both lists.
  • Favourites: maximum 20.
  • Contact data is read-only; the owner’s only editable fields on a connection are circle, relation, nickname and favourite position.
  • Removing a contact, or moving them out of a circle a connected service syncs, moves that person’s entry in every connected address book to the To delete bucket. The entry is removed from the address book only when that service’s Allow Incico to delete is on; otherwise it is reported and left alone.
  • When a connection’s profile is deleted the card disappears from the list at the next refresh, without a notification (A102).