Messages and links
Inboxes and messages between people, and links between records.
Inboxes and messages
addressed and direct records carry a recipient, named on the
call you already make. The recipient is server-stamped
as record.audienceUserId, never a data field.
// addressed (owner → person): your server sends this, e.g. "Mark shipped"
await updates.create({ text: "Your order shipped 🎉" }, { for: userId })
// direct (person → person): DMs, group chats, sharing, requests
await messages.create({ text: "hey!" }, { for: otherUserId })
// a group chat: each message names the group (up to 20 people), one record they all read
await messages.create({ text: "Dinner at 8?" }, { for: [annaId, benId, chrisId] })
// The reader's side is list(): the server returns only their own inbox
const { records } = await updates.list({ sort: "newest", limit: 20 })
- Same message for everyone →
admin_write. A specific thing for a specific person →addressed. - An
addressedrecord is sent from your Gemmein dashboard, a relay or your server (a secret key, naming the person withfor). No app session sends one, yours included: the signed-in app is always a person reading their own inbox, and a send from it is403 forbidden("only the app owner sends addressed records"). - For inboxes, use
watch(): it pollslist({ since })every 10 s (5 s floor), hands you only what changed or was deleted, and sleeps while the tab is hidden. Never write a tight loop by hand. Read-state lives in the person's own private collection. directis messaging inside your app, and your Gemmein dashboard can read every message. Never present it as private or encrypted chat.- Group chats: a
directmessage can name up to 20 people. Each message is one record they all read, which only its author edits, andrecord.recipientslists them. Anyone in the group replies the same way, naming the others. On a reply-only collection, everyone named must have written to the sender first. For more people, use asharedcollection, or send oneaddressedrecord each. - Who can start a conversation on a
directcollection is itsdirectInitiationsetting, which your human picks on the collection's page in the dashboard's Data:"anyone"(the default),"invited"(reply-only: a person writes only to someone who has already written to them in this collection) or"off"(no one sends from the app; existing messages stay readable). Your dashboard and a secret key are never held to reply-only. - The error names the fix:
invalid_audience(the recipient isn't a person in this app),reply_only(this collection only allows replying to people who wrote first),sends_disabled(you turned direct messages off for this collection).
Links between records
Store another record's id in a field and it becomes a link. On collections that support
links (community, shared, direct) you can embed the
linked records on reads with expand:
const { records } = await posts.list({ expand: ["authorId"] })
records[0].expand.authorId // the linked record, or null if gone (tombstone)
expand throws on private,
public_read, and admin_write, because those rules have no link
shape; join in memory there. Up to 3 fields per call.
Was this page helpful?
Thanks for telling us.
Thanks for telling us.
Last updated