Changelog

Every change to the live product, dated.

Every change that reached the live product, newest first.

8 Sep 2026Gemmein on a phone, and the people you are billed for

  • Mobile is a first surface. @gemmein/sdk/expo is a second entry point of the same package, with a phone's answers filled in: the session in the iOS Keychain or Android Keystore, sign-in that survives the app going to the background, and streaming AI answers on React Native. GemmeinSwift is the Swift package for iOS 17+ and macOS 14+ — the same method names, in Swift idiom, with no dependencies. A test holds the two to the JavaScript SDK's own surface, so a method that exists on one exists on all three.
  • An id for the app stores, that names nobody. Every signed-in person now has a storeAccountToken: an opaque id you hand to RevenueCat as the app user id (or to Apple as appAccountToken). A store's webhook resolves to exactly one person without your app key or their email ever entering a third party's ledger.
  • App-store purchases, as three relays. The Relays room offers RevenueCat as three ready definitions — purchase fulfils the product, expiry revokes access, cancellation revokes access — each its own webhook with its own secret. The two that take access away carry their own guard, so a purchase posted to the wrong URL can never revoke what it paid for. A relay can now name the person by person_id or person_token as well as by email.
  • You are billed for active people. A person counts once in a month, on their first authenticated request of the month, on web and mobile alike — polling counts, signed-out visitors never do, and you and your admins never do. It used to be counted from sign-ins, which billed for a month someone signed in and never for the months they used the app. Development never bills. The Usage dial, the people ceiling and the invoice read one number.
  • Sign-in emails are counted, and never capped. Your Usage page shows how many sign-in emails your app sent today and this month, and we tell you when a day is far above your own normal. There is no daily limit and none was added: the only bounds are the two that always existed — three per address and twenty per IP per app, each per fifteen minutes.
  • An AI tool can be written before its key. Saving a tool no longer needs a provider key in place; the tool is listed as waiting on one. A run still refuses until a key is there, before a single credit moves.
  • Versions. Engine 0.11.0, @gemmein/sdk 0.10.0, @gemmein/mcp 0.7.0, GemmeinSwift 0.10.0.

7 Sep 2026Keys that know what they are, production sync, and AI tools that live on the server

  • Three kinds of key. A server key runs your app and nothing else. The CLI key is minted with your app and shown on Setup; it is what npx gemmein sync and npx gemmein go-live act with, and linking is one paste. A sync key is minted in production with your sign-in code, lives one hour, and carries relays and AI tools into production with npx gemmein sync --live. Every key can be rotated; historic keys stay listed with what they did.
  • Sync never deletes, and asks before it replaces. Every relay and AI tool says where its definition came from: from a file, or edited in the dashboard. A row edited in the dashboard is asked about before a file replaces it. A relay's name is fixed once created.
  • AI tools are defined on the server. The instructions, the prompt template, the inputs a tool accepts, the model and the caps live on the tool. Your app calls a tool by name with inputs (g.ai.run); the prompt never ships in the browser. Raw provider calls from the browser are off until you switch them on per key.
  • Every AI call is recorded per person. The tool, the cost, tokens, how it ended, when. What people asked and got back is kept only for a tool you switch that on for, is read by the person and by you, and leaves with the person when they are erased.
  • Versions. Engine 0.10.0 (the gemmein launcher 0.10.0), @gemmein/sdk 0.9.0, @gemmein/mcp 0.6.0.

6 Sep 2026The Logs calendar, and a console that says when it could not look

The audit room shows one day at a time, and across the console a read that failed says so instead of standing in for a fact.

  • One day at a time. The Logs room shows a single day, steps back only to days that have records, steps forward once you have gone back, and opens a calendar in which only days with something on them can be chosen. Days are counted where you are, not in UTC, and every time on the page is shown in your own zone. Behind it, an index on the audit log that reads only what it answers with: an empty stream costs one seek, however long the history.
  • A read that failed is not an answer. A number the console could not fetch says it could not load, never a zero, an empty list or a green tick. A delete dialog never calls a collection empty because a count did not come back. The go-live gate and the health signal draw only from reads that answered.
  • Requests give up and say so. A read Gemmein does not answer in ten seconds ends with a sentence rather than a spinner; a change that may already have gone through tells you to look before trying again. A support reply that timed out is reported as unknown, never as unsent.
  • Every refusal carries its real status. The API's refusals travel as their own 4xx with their own code and sentence; nothing readable rides a 500 any more. Sign-in fields that are missing or too long are named: missing_params, field_too_long.
  • One sender per domain. A sending domain belongs to one app across the platform. A domain add the mail provider did not answer is reported as unknown, and the next attempt picks up what the provider holds rather than fighting it.

6 Sep 2026The console, room by room

The admin dashboard is rebuilt on one structure: a room is a list, a record replaces it, and the rail goes away when you are inside one.

  • One structure. Every room is one card; a long list scrolls inside its own box; actions say what they do and sit behind a Manage door; a record replaces the list and a back arrow returns to it. Light by default, dark by choice, on every width.
  • AI tools has its own room. The keys you bring from a provider and the tools you price on them live together, apart from the secret keys Gemmein mints for your app. Creating a tool with a name that exists is refused; edit the one you have.
  • Logs go back. The audit room pages older days on an exact cursor and can be asked for a date window, on every stream. Older days arrive as more day blocks; the door appears only when there is another page.
  • Sign-in finishes itself. Paste the eight-digit code anywhere on the page and it fills and submits once.

5 Sep 2026Products sold any way, and AI operations you price

A product is sold through Stripe, through any provider you connect with a relay, or not yet. An AI call is a named operation with the credit price and the access gate you set.

  • Products are provider-neutral. On the Payments page each product says how it is sold: a Stripe Payment Link, a relay, or not yet. Two relay actions, fulfil_product and refund_product, grant and take back a product’s key and credits from any provider’s webhook, GoCardless, Paddle, Lemon Squeezy or a bank transfer, through the same path the Stripe receiver uses. A ref is the provider’s own payment identifier and is honoured once. g.payments.buy answers product_not_sellable for a product without a Payment Link.
  • AI tools. A tool is a named AI operation with a provider, an optional pinned model, a credit price from 1 to 10,000 and an optional access gate. g.ai.chat and g.ai.text take { tool }; the price, model and gate come from your configuration and never from the browser. Refusals name the tool: “Deep Research requires Pro Max.” and “Deep Research costs 20 credits. You have 7.” A call that names no tool runs as the default tool at one credit. Tools live in the Keys room, in gemmein/ai/tools/<name>.json on the local rail, and travel with gemmein sync. This replaces the flat one credit per call.
  • Back office. The person page lists every spend with the tool’s name and the balance it left; Logs tell when a tool is set or removed and when a relay fulfils or refunds a product; removing a key or a tool is a step-up action.
  • Identity. A person is one person whatever the casing of their email.
  • The codes. product_not_sellable, relay_missing, unknown_tool, tool_disabled, entitlement_required, model_pinned, provider_not_configured, too_many_tools, invalid_tool. Engine 0.9.0, @gemmein/sdk 0.8.0, @gemmein/mcp 0.5.0.

4 Sep 2026Credits and the AI route

Your customers can hold a balance your product spends, and your app can call OpenAI, Anthropic or Google through Gemmein on your own key.

  • Credits. A product with “Grants credits” adds that many to the buyer’s balance at each confirmed purchase, and a full refund claws back what is still unspent, floored at zero. Your server spends with a reason behind a fourth key capability, “Spend a person’s credits”, at most once per key and person; a relay grants with grant_credits; the customer’s page shows the balance and the ledger and lets you comp with a note. Credits never expire and are never priced by token. Engine 0.8.0, SDK 0.7.0.
  • The AI route. Paste your OpenAI, Anthropic or Google key once in the Keys room, where it is write-only from then on, and a signed-in person’s chat request reaches the provider through Gemmein for one credit, the body forwarded as sent and the answer streamed back. A provider failure before the first byte refunds the credit. The Usage room counts the calls; your provider bills the tokens on your own account.
  • The codes. credits_exhausted carries the balance; credits_ceiling, dedupe_conflict, invalid_amount, invalid_reason, invalid_key, person_not_found, capability_required, invalid_body, ai_not_configured, provider_required, model_not_allowed, ai_capped, payload_too_large, session_required, scope_denied, provider_unreachable and, from g.ai.text, provider_error each say what to do. Both guides, Credits and The AI route, open with what each does and does not do.

4 Sep 2026Relays and the invite door

A provider’s webhook, a schedule or a record change now runs Gemmein’s own verbs for you, and your server can create a person by email before they sign in.

  • Relays. One JSON file names a trigger and up to ten actions. The trigger is a webhook from any provider that signs its calls, such as GoCardless, Stripe or a svix-based sender, a schedule from every 15 minutes to daily at a UTC time, or a record created, updated or deleted in one of your collections. The actions are Gemmein’s own verbs: write a record, grant or revoke access, email the person, or call a URL on your host with a signed body. Every event is recorded before anything runs, retried on failure, and dead after seven attempts with an alert and a replay button. The dashboard shows each relay under Setup with its URL, its secrets shown once, and every event with each action’s result. Gemmein runs no code of yours inside one; compute stays on your host. Engine 0.7.0.
  • A seventh grant source. Access a relay grants is recorded as relay, alongside the six that exist. The customer’s page reads “granted by relay <name>”, and no secret key can create one. SDK 0.6.0 lists it by kind in holdings.
  • The invite door. A secret key holding “Create a person by email before they sign in” creates a person by email or returns the one that exists, so an invoice, a client portal or a booking-winner email can address someone who has never signed in. The person shows as invited until their first sign-in and is not counted in the people band before then. 500 invites per app per day, raised on request. SDK 0.6.0.
  • A sixth alert. A relay that stops after its retries emails you once, with the relay’s name and the last error, and links to the event so you can replay it. On by default; a switch on the Alerts page.
  • Page limits are refused, not clamped. list() with a limit above 100 answers invalid_limit. The default stays 25 and the maximum 100. SDK 0.6.0.

3 Sep 2026Server gate: verify sessions and grant access from your own code

Code you run anywhere can now ask Gemmein who a person is and what they hold, and secret keys leave a trail you can read.

  • The server gate. A function on your own host, such as a model call, a PDF render or a nightly job, asks Gemmein three questions with a secret key: who is this person, what do they hold, and change what they hold. One call answers identity and holdings together. Holdings are the person’s current access keys and live grants, with source kind and expiry. They never include billing data or Stripe identifiers. Every refusal says why and what to do next: a session that expired, was revoked, or belongs to a suspended person each gets its own answer. Engine 0.6.0, SDK 0.5.0.
  • Two capabilities on a secret key. Verifying a session needs nothing extra. Looking a person up by id, and granting or revoking access, are new power, so they sit behind two checkboxes you tick when you mint the key. A key grants trials, comps and promotions; purchases and subscriptions still come only from Stripe. The key you get from gemmein dev holds both, so the whole loop works on your machine. Launcher 0.6.0.
  • Secret keys are on the record. Every call a secret key makes, allowed or refused, lands in that key’s usage ledger: last used, calls and refusals for the week on the Keys page, the full day-by-route table in the key’s own room. Refusals reach your activity log, one line per kind each hour, never a flood. A grant made by a key names the key on the customer’s page and in the log, with what changed, and never records it as done by hand. Engine 0.6.0.
  • Grant model documented. The docs now carry the whole model: the two kinds of grant, purchase-tied ones written only by Stripe and by-hand ones written only by you, what ends each, which counts as revenue, and what to do when a refund meets access. Consumable credits are described as planned and not yet available.

27 Aug 2026Files that must be real, and a longer proof

A record can no longer point at a file that doesn’t exist, and the proof script grew from four checks to sixteen.

  • File references are checked everywhere. Any field that carries a file reference must name a real, finished upload the writer is allowed to hand over, on any collection, under any rule, written by your app or from the admin dashboard. A made-up reference, or someone else’s, is refused with a plain reason that names the field. Engine 0.4.8.
  • reaffirm now runs sixteen checks. Apps copy reaffirm into CI to prove their own boundaries on every deploy. It now checks sixteen things: private records stay private between two real users, drafts never show anonymously, records addressed to one person are read by that person only, plan-gated collections refuse people without the plan, a file reference that names nothing is refused, HTML into a plain-text field is refused, and more. Each check prints what it proved or why it was skipped; a broken config exits with instructions to fix it, never a false pass. SDK 0.4.6.

26 Aug 2026Live lists, emails from your app, and a file you hand to one person

Your app can keep its lists current, email its own customers, and files now go both ways.

  • Live lists. Your app can ask for what changed every few seconds, including new records, edits and deletions, through the same safety rule as any read. Nothing is pushed, nothing to configure; the SDK’s watch() does the asking, backs off when the tab is hidden, and catches up when it returns. Engine 0.4.5.
  • Your app emails its customers. Your app’s server can email its own signed-in people: order shipped, booking confirmed. Account and security notices skip that cap (security notices are exempt from the event-email cap); everything else is capped at five per person per day, so a bug can never flood an inbox, and the whole app shares an hourly ceiling. Every email says why the person is receiving it, lands as a thread in your Inbox, and is on the record. Engine 0.4.6.
  • Hand a client a file. Files go both ways now. From the paperclip in the dashboard you can hand a document to one customer. Only they can read it, and every handover is on the record. Apps can do the same in code. Engine 0.4.7.
  • File fields carry their kind. A collection field that holds files is learned as a file field, image or document, from the real uploads. The dashboard then offers a picker that accepts the right kind (an image field takes images, a document field documents), and on live collections a wrong-kind attach is refused with a plain reason instead of saved wrong. Engine 0.4.7.

23 Aug 2026Alerts, and usage bands that bill rather than block

Gemmein now tells you about your business by email, and reaching a ceiling no longer stops your app.

  • Outgrowing your band never stops your app. It keeps running. You're told once, and next month you're billed the band your people landed in. Hold at your current band any time from Usage & billing.
  • Storage is the one thing that can fill: you're told at 80% and at full, uploads keep working up to twice your storage allowance, and are refused past that until you delete files.
  • Alerts is a new page under Audit. Five emails, from alerts@gemmein.com (replies reach hello@gemmein.com): you outgrew your band · a customer has been waiting 12 hours · your card failed · your domain or email forward stopped working · storage is 80% or full. The two about your bill are always on; the rest are switches. Every send is listed on the page.
  • Your development sandbox never gets a bill email, because development is free.
  • Start a conversation from the Inbox. Pick one of your customers, a subject and a message; it sends under your app’s name and their reply lands in the same thread. Your customers only; the Inbox isn’t a mail client for strangers.
  • Documents. Uploads now take PDF, ZIP and EPUB alongside images, and every file can be 25 MB (was 10, images only). A document always downloads and never opens inside your page. In the dashboard, the paperclip on a record field takes documents too and no longer fails on files over 1 MB. Downloads carry the file’s own name. Engine 0.4.4 applies the same rule locally.
  • For developers: npx gemmein dev prints the same alerts in the terminal, the way sign-in codes print, so your AI sees them while you build. Engine 0.4.3.

22 Aug 2026Email from your own domain

Sign-in codes and your replies now go out from one address on your domain, and customer replies land in your Inbox.

  • Connect your domain on Setup → Domains. You pick the part before the @, such as hello, support or contact, and that one address is what customers see in both directions.
  • Before any records are generated, the dashboard checks whether your domain already handles email and says plainly which way it will be set up, with a preview of how the email will look on a phone.
  • Add the records shown (host, type, value, TTL, priority). Sending and receiving verify separately; the card shows verified state, not claimed state, and Check now tells you what it found.
  • Replies to a customer's message go out from the same address, threaded under the original. While receiving is on, every email your app sends carries that address as its reply-to and in its footer; while it's off, emails stand alone.
  • If your domain already has a mailbox and you'd rather read there, set a forward. The dashboard sends a message through it and only marks it working once it arrives, never on your word.
  • Your domain's own mail records are never touched. Receiving lives on a subdomain we manage; your apex stays yours.

22 Aug 2026Plans unlock by name

A plan or product unlocks a collection by being the plan. Access keys left the dashboard.

  • On a collection's card, Unlocked by is now your plans and products, ticked by name. Tick several and any one of them opens it: “anyone on pro or studio can get in”.
  • The “what this plan unlocks” field on the Payments page is gone. A plan or product card says what it unlocks, worked out from the collections locked to it.
  • Granting access by hand from a customer's page is by plan or product name, such as “pro (plan)”, and the audience view is titled “Who is on pro”.
  • For developers: every plan and product carries one key, access:<slug of its name>, minted when it is first saved and kept through a rename. 403 entitlement_required carries requires: one key, a string, the first of them when several open the collection. The full list never leaves the server.
  • Existing apps: a plan that already granted a key keeps its first one as its key. A collection locked to a key no plan carries any more shows as locked to “a removed plan or product”; tick the right plan to relock it.

18 Aug 2026The dashboard, rebuilt

The dashboard was rebuilt around six places, named for what an owner does there.

  • Navigation is Today · Inbox · Customers · Revenue · Setup · Audit. Setup holds Payments, Domains, Collections, Your app and Go live; Audit holds Logs, Usage & billing and Secret keys.
  • Before go-live, home is your Setup journey, and each step says what it is for and what it needs. At go-live it becomes Today.
  • Inbox lists every customer message as a thread, with the ones waiting on you first. Reply from the dashboard.
  • Payments is a numbered procedure: connect Stripe, paste back what it gives you, name a plan or product. Plans and products are cards you can open.
  • Empty screens say one thing in the middle of the page. Light theme by default; glass is solid enough to read through.

30 Jul 2026One banded plan + files on products

Pricing became one plan whose bill follows your people, and selling a downloadable file stopped needing anything but the product card.

  • One live plan: the bill is your verified people, moving in five agreed steps of $50, $75, $100, $125 and $150, priced on last month's count. $150 is the cap; past it, we talk. Hold at your current band any time from the dashboard.
  • Ceilings follow your people too: room grows automatically as your business grows, floors stay generous, and reaching a ceiling never generates a charge. Storage is now measured in real bytes.
  • Sell a file by attaching it to the product: pdf, zip, epub, audio or image, up to 100MB. Buyers reach it through their purchase (g.purchases.mine() + g.files.link); a refund cuts it off, a partial refund doesn't. No receipts collection needed.
  • Usage-limit errors now say when they reset (resetAt on monthly counters; storage frees by deleting files).

29 Jul 2026Sealed file delivery

A file's URL is no longer proof of access. Private files now resolve through short-lived signed links, checked against your rules on every request.

  • Files in collections a stranger can read keep permanent public URLs, byte-identical to before.
  • Files in every other collection live in a sealed keyspace: g.files.link(ref) asks your collection's rule first, then returns a link that expires in minutes.
  • Revoking access (a refund, a revoked grant) stops new links immediately; an already-issued link dies at its expiry.
  • Breaking change: upload() now returns a ref, not a url. Store the ref; call g.files.link(ref) when you need a URL.
  • Every sealed link minted is recorded in Gemmein's operational logs: who was handed what, and when.

28 Jul 2026Access entitlements

Payment now means something the engine enforces: buying grants access, refunding revokes it.

  • Collections can require an entitlement: requires: "access:pro" gates any rule for your customers, with no code written by you (your own server keys are exempt by design).
  • Paid checkouts and subscriptions create grants automatically; a full refund revokes exactly the grants it created and nothing else.
  • Owners can grant and revoke access by hand from the dashboard for trials, comps and support fixes, without touching revenue records.
  • Buyers keep proof of purchase via g.purchases.mine(), whether or not the app keeps a receipts collection.
  • A signed-in customer without access gets 403 entitlement_required naming the missing key, so your app can show an upgrade screen instead of an unexplained failure.

25 Jul 2026CLI 0.1.2

The CLI now handles non-interactive shells, which is how coding agents run it.

  • npx -y gemmein init in a non-interactive shell now hands off cleanly to a human instead of crashing. The three setup questions are business decisions for you to answer.
  • Every command that needs a project answers consistently; local shape learning fixed so gemmein go-live can verify what you built.

24 Jul 2026Local runtime + pricing states

Build and rehearse everything on your machine, free; pricing became two simple states.

  • npx gemmein runs the real production engine locally: same rules, same refusals, no account needed, state in one file.
  • Pricing: free while you build; Live from $50/month when real customers arrive. No card at signup, ever.
  • gemmein feedback sends a note straight to the founder.

20 Jul 2026Docs + MCP

The teaching surface for humans and their AIs.

  • docs.gemmein.com went live with the full builder guide, error tables, and llms.txt.
  • @gemmein/mcp published: your coding agent can read Gemmein's rules, field structures, and guides directly.

19 Jul 2026Gemmein launches

The platform, the dashboard, and the SDK went public.

  • Passwordless sign-in, seven storage rules, file uploads, and Stripe-backed subscriptions went live in production.
  • @gemmein/sdk published to npm; the dashboard opened for signups at app.gemmein.com.