Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .claude/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4900,3 +4900,50 @@ as a side effect of a keyboard shortcut.

**Applies to:** `src/hooks/use-history.ts` (new), `src/components/resume/resume-editor.tsx`,
`src/components/resume/fit-panel.tsx` (its `onChange` now names what it cut).
## 2026-09-03 — Any mailbox: Microsoft 365, IMAP and CalDAV, behind one reader

**`GoogleAccount` became `LinkedAccount`, several per person.** A `provider` column
(GOOGLE, MICROSOFT, IMAP), provider-neutral `features` ("mail", "calendar") in place of
Google's scope URLs, and the IMAP and CalDAV fields on the same row. The migration renames
rather than recreates, so a Google connection made on the previous release survives with
its scopes rewritten. Unique on (userId, provider, email) rather than userId, because a work
Outlook and a personal Gmail are both where recruiters write, and every read merges across
them with the account named on each thread and event.

**One interface, three wire protocols.** `src/lib/accounts/types.ts` defines `MailReader`
and `CalendarReader`; `google.ts`, `microsoft.ts`, `imap.ts` and `caldav.ts` implement them
and nothing else in the app knows which answered. Read-only is enforced by the interface
having no write, not by convention. The data layer resolves credentials — refreshing OAuth
tokens, keeping Microsoft's rotated refresh token — and collects per-account failures into
`warnings` so one dead token never hides the other inbox.

**Two libraries, on purpose, for the protocols nobody should hand-roll.** Google and
Microsoft are six HTTP requests each and stay hand-written. IMAP is a stateful protocol with
thirty years of server quirks and MIME is worse, so `imapflow` and `mailparser` do that;
CalDAV discovery differs per server and ICS recurrence is its own specification, so `tsdav`
and `ical.js` do those. They are the largest dependencies in the app and load only when an
IMAP account is read. Threads on IMAP are joined by Message-ID and In-Reply-To, which is
what every mail client does; a reply whose client dropped the header is its own thread.

**App passwords, verified before they are stored.** `connectImapAccount` logs in to both
servers first, so a wrong password is an error in the form rather than a broken tile.
Stored as issued, like every other credential here. `connect_imap_account` exists as a tool
because MCP-first means it must, and its description says never to repeat the password.

**A hostname in a form is a request the server makes.** `assertReachableHost` refuses
loopback, link-local (where cloud metadata lives), unspecified and multicast targets for
both IMAP and CalDAV, resolving names so an A record at 127.0.0.1 is refused like the
literal. Private ranges are allowed on purpose: a mail server on a home LAN is a real
reason to self-host, and loopback and metadata are where the damage is.

**No SMTP.** The app never sends on anyone's behalf; read-only is what makes handing over an
inbox safe. Sending is a product decision to make on purpose, not a side effect of "support
IMAP".

**Slack and Discord are on the picker as coming soon**, greyed, because the user asked for
them to be visible before they exist. Nothing behind them yet.

**Applies to:** `prisma/schema.prisma`, `src/lib/accounts/`, `src/lib/data/accounts.ts`
(replacing `data/google.ts`), `src/lib/settings.ts`, `src/app/api/auth/microsoft/`,
`src/lib/mcp/{tools,handler}.ts`, `src/server/actions.ts`, `src/components/settings/`,
`src/components/admin/configuration-panel.tsx`, and the manual.
86 changes: 50 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ just *talk* to it.
before it does it. Names fold case, so `linkedin` lands on the `LinkedIn` you already
have rather than minting a twin.
- **AI connections** — every person gets their own URL that turns all of the above into
122 tools any MCP client can call (152 if you're an admin). Claude, Claude Code, ChatGPT,
124 tools any MCP client can call (156 if you're an admin). Claude, Claude Code, ChatGPT,
Cursor, VS Code and Windsurf all have one-paste setup built into the app.
- **Multi-user** — invite whoever you like. Each person gets a completely private workspace;
admins manage accounts but never see anyone's career history, resumes or applications. Admin lives
Expand All @@ -107,12 +107,14 @@ just *talk* to it.
every migration finished, whether the last invite email actually left, and whether Stripe
is still calling the webhook, then lists what has failed in the last thirty days. Ask an
assistant for `admin_health` and you get the same answer without opening a browser.
- **Your inbox and calendar, on the record** — connect your own Gmail and Google Calendar
under Settings → Connections and every contact, company and application shows the real threads
and meetings behind it, under the timeline of what you logged. Interviews you accepted in
Google Calendar land on the pipeline's calendar view. Read-only and live: nothing from
your account is copied to the server, and an assistant asked where an application stands
reads the recruiter's reply instead of guessing from a stage.
- **Your inbox and calendar, on the record** — connect your own mail and calendar under
Settings → Connections — Google, Microsoft 365, or anything that speaks IMAP and CalDAV,
and more than one if recruiters write to more than one — and every contact, company and
application shows the real threads and meetings behind it, under the timeline of what you
logged. Interviews you accepted in your real calendar land on the pipeline's calendar view.
Read-only and live: nothing from any account is copied to the server, and an assistant
asked where an application stands reads the recruiter's reply instead of guessing from a
stage.
- **Sign in how you like** — email and password always work, and an instance that adds a
Google OAuth client gets a Continue with Google button as well. Google never bypasses an
invitation: it signs in people who already have an account or an unexpired invite, and
Expand Down Expand Up @@ -265,7 +267,7 @@ config already filled in with your URL, ready to copy.
| **Anything else** | A standard `streamable-http` entry — or `mcp-remote` if it only speaks stdio |

Hit **Test** next to any connection and the app calls its own endpoint the way a client
would, then tells you how many tools answered — 122, or 152 if you're an admin.
would, then tells you how many tools answered — 124, or 156 if you're an admin.

#### One connection per client

Expand Down Expand Up @@ -392,26 +394,38 @@ accounts here are matched by address, so that check is what the whole thing rest

By conversation: `admin_get_google_config`, `admin_set_google_config`.

### Gmail and Calendar (optional, per person)

The same OAuth client lets each person connect their own Gmail and Google Calendar under
**Settings → Connections**. Google asks for read-only access to both; either can be left
unticked. From then on a contact's page shows the threads with their address and the
meetings they are invited to, a company's page shows everything from its domain, an
application's page shows both under its timeline, and the pipeline's calendar view carries
the interviews from the real calendar. Nothing from anyone's account is copied to the
server: every page asks Google when it opens, and disconnecting revokes the token and
deletes the only thing held.

Two things in the Cloud console make it work: enable the Gmail API and the Google Calendar
API in the project, and add the `gmail.readonly` and `calendar.readonly` scopes to the
consent screen. Gmail's read scope is one Google calls restricted, so leave the consent
screen in Testing and list the people who will connect as test users rather than going
through verification for an instance you host for friends.

By conversation: `get_google_connection`, `list_correspondence`, `search_email`,
`get_email_thread`, `search_calendar`, `disconnect_google`, and the `inbox_review` workflow
that reads a week of mail and proposes what to log.
### Mail and calendar (optional, per person)

Each person can connect the accounts recruiters actually write to, under **Settings →
Connections**, and the app reads them live: a contact's page shows the threads with their
address and the meetings they are invited to, a company's page shows everything from its
domain, an application's page shows both under its timeline, and the pipeline's calendar
view carries the interviews from the real calendar. More than one account merges. Nothing
from any account is copied to the server: every page asks the provider when it opens, and
disconnecting revokes what can be revoked and deletes the credential.

Three kinds of account:

- **Google** uses the sign-in client above. Two more things in the Cloud console make it
work: enable the Gmail API and the Google Calendar API, and add the `gmail.readonly` and
`calendar.readonly` scopes to the consent screen. Gmail's read scope is one Google calls
restricted, so leave the consent screen in Testing and list the people who will connect as
test users rather than going through verification for an instance you host for friends.
- **Microsoft 365 and Outlook.com** need an app registration in Microsoft Entra, set under
**Admin → Configuration → Accounts**: supported account types set to any directory plus
personal accounts, a Web redirect URI the screen shows you, the delegated Graph permissions
`Mail.Read`, `Calendars.Read`, `User.Read` and `offline_access`, and a client secret.
- **Anything else** — Fastmail, iCloud, Yahoo, a university account, a self-hosted server —
connects by IMAP and CalDAV with an app password, from a form with presets for the common
ones. It needs nothing from an admin. Either half can be left out.

The app never sends, so there is no SMTP to configure: read-only is what makes handing over
an inbox safe, and the permissions it asks for cannot do anything else.

By conversation: `list_linked_accounts`, `connect_imap_account`, `test_linked_account`,
`disconnect_account`, `list_correspondence`, `search_email`, `get_email_thread`,
`search_calendar`, and the `inbox_review` workflow that reads a week of mail and proposes
what to log. Admins: `admin_get_microsoft_config`, `admin_set_microsoft_config`.

### Everything else you can change

Expand Down Expand Up @@ -443,13 +457,13 @@ By conversation: `admin_list_variables`, `admin_set_variable`, `admin_delete_var

## What your AI can do once it's connected

122 tools. One hundred and fourteen of them are the data tools across the four areas, the
archive that cuts through all of them, your Gmail and Calendar, and your account; the other
eight are the workflows below, published as tools as well as prompts, because prompt support
is optional in MCP clients and tool support isn't. Call one and it hands back a step-by-step
plan that it then follows. Admins get 30 more — 29 data tools and a ninth workflow — and
members never even see those in the tool list, so nobody is tempted by a permission they
don't have.
124 tools. One hundred and sixteen of them are the data tools across the four areas, the
archive that cuts through all of them, your mail and calendar accounts, and your own
account; the other eight are the workflows below, published as tools as well as prompts,
because prompt support is optional in MCP clients and tool support isn't. Call one and it
hands back a step-by-step plan that it then follows. Admins get 32 more — 31 data tools and
a ninth workflow — and members never even see those in the tool list, so nobody is tempted
by a permission they don't have.

| Workflow | What it does |
| --- | --- |
Expand All @@ -460,7 +474,7 @@ don't have.
| **Research a company into the CRM** | Gathers what's known, works out what's missing, and writes it back to their record without flattening what was already there. |
| **Prepare for an interview** | Pulls the posting, the timeline, the company research, the people involved and your own evidence into one prep sheet. |
| **Log what happened this week** | You ramble; it files everything to the right role, application, or note. |
| **Bring the pipeline up to date from your inbox** | Reads a week of your Gmail and Calendar, tells you what moved, and proposes what to log — nothing is written until you say yes. |
| **Bring the pipeline up to date from your inbox** | Reads a week of your mail and calendar, tells you what moved, and proposes what to log — nothing is written until you say yes. |
| **Invite and onboard someone** *(admin)* | Invites a person, hands you the link if email isn't set up, and drafts the message to send them. |

Every client is instructed never to invent experience, employers, dates, or metrics. If there's
Expand Down
3 changes: 2 additions & 1 deletion docs/app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ pasting a URL into an assistant, not changing a password.
<Card title="Connections" icon="plug">
One row per thing wired to your workspace, each saying whether it is on and whether it
needs you. Open one for its URL, setup steps, test, rename, rotate, disconnect —
or, for [Gmail and Calendar](/guides/inbox-and-calendar), to connect and revoke.
or, for a [mail and calendar account](/guides/inbox-and-calendar) — Google, Microsoft 365,
or IMAP and CalDAV — to test, reconnect and disconnect.
**Connect** opens a library of everything you can add, assistants on one tab and
accounts on the other, with what you already have marked as connected. Plus the three
[skills](/skills), each as a raw `SKILL.md` and as a zip.
Expand Down
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
"tools/pipeline",
"tools/crm",
"tools/archive",
"tools/google",
"tools/accounts",
"tools/connections",
"tools/admin"
]
Expand All @@ -163,6 +163,7 @@
"self-hosting/configuration",
"self-hosting/email",
"self-hosting/google",
"self-hosting/microsoft",
"self-hosting/pdf",
"self-hosting/billing",
"self-hosting/upgrading"
Expand Down
48 changes: 33 additions & 15 deletions docs/guides/inbox-and-calendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,35 @@ calendar view shows the interview you accepted in Google Calendar, and an assist

## Connecting

**Settings → Connections → Google → Connect Google.** Google asks for read-only access to Gmail and
Calendar; you can leave either unticked and connect the other. The account you connect does
not have to be the one you sign in with.
**Settings → Connections → Connect an account.** Three kinds, and you can connect more than
one if recruiters write to more than one:

- **Google.** A consent screen asks for read-only access to Gmail and Calendar; leave either
unticked and the other still connects. The account does not have to be the one you sign
in with.
- **Microsoft 365 or Outlook.com.** The same, through Microsoft's consent screen, for a work
account or a personal one.
- **Anything else** — Fastmail, iCloud, Yahoo, a university mailbox, a self-hosted server.
A short form takes the IMAP server and the CalDAV URL, with presets for the common
providers, and an **app password** from your provider's security settings. Never the
account password. Both servers are logged in to before anything is saved, so a wrong
password is an error now rather than a broken tile later. Either half can be left blank.

<Note>
Nothing from your account is copied to the instance. Every page and every tool asks
Google at the moment you open it, and disconnecting deletes the only thing held — the
token. The app cannot send, accept, archive or delete anything; the permission it asks
for does not allow it.
Nothing from any account is copied to the instance. Every page and every tool asks the
provider at the moment you open it, and disconnecting deletes the only thing held — the
token or app password. The app cannot send, accept, archive or delete anything; the
permissions it asks for do not allow it, and there is no SMTP to configure because it never
sends.
</Note>

If the button is missing, the instance has no Google OAuth client yet. That is an admin's
job, under Admin → Configuration → Sign-in, and it is the same client that powers
Continue with Google — see [Google sign-in](/self-hosting/google) for the two extra
switches Gmail and Calendar need.
If Google or Microsoft is missing from the picker, the instance has no client for it yet.
That is an admin's job: [Google sign-in](/self-hosting/google) for Google, which powers
Continue with Google too, and a [Microsoft app registration](/self-hosting/microsoft) for
Microsoft. IMAP and CalDAV need nothing from an admin.

Open any account's tile to **Test** it — one read from each half, with the provider's own
words when something is wrong — rename it, reconnect it, or disconnect it.

## What shows where

Expand All @@ -37,11 +51,13 @@ switches Gmail and Calendar need.
happened.
- **A resume** — the mail behind every application it went out with, in the evidence
panel's "Sent to" tab.
- **The pipeline calendar** — meetings with anyone on the pipeline appear alongside your
follow-ups and tasks, and `list_schedule` returns them the same way.
- **The pipeline calendar** — meetings with anyone on the pipeline, from every connected
calendar, appear alongside your follow-ups and tasks, and `list_schedule` returns them the
same way. The same interview on a work and a personal calendar shows once.

Open a thread on any of those and it expands in place; **Open in Gmail** takes you to the
real thing.
Open a thread on any of those and it expands in place; **Open in Gmail** or **Open in
Outlook** takes you to the real thing where the provider has somewhere to go. An IMAP
account has no web client to link to, so its threads only expand.

## What to say

Expand All @@ -50,6 +66,8 @@ real thing.
- *"Did any rejections come in this week?"* — `search_email`.
- *"What interviews do I have next week?"* — `search_calendar`, or `list_schedule` for
the whole week including follow-ups.
- *"Connect my Fastmail — here's an app password."* — `connect_imap_account`, which logs in
before saving and never repeats the password.
- *"Go through my inbox and bring the pipeline up to date."* — the `inbox_review`
workflow: it reads every open application's mail, tells you what moved, and proposes the
`log_activity` and `move_application_stage` calls for you to approve one by one. It
Expand Down
6 changes: 4 additions & 2 deletions docs/self-hosting/google.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ changes either way, so it is a safe thing to try.

The same OAuth client lets each person connect their own Gmail and Google Calendar under
**Settings → Connections**, so the app can show the threads and meetings behind their contacts,
companies and applications. Two more things in the Cloud console make that work:
companies and applications. (Microsoft 365 has [its own page](/self-hosting/microsoft); IMAP
and CalDAV accounts need nothing from an admin.) Two more things in the Cloud console make
Google work:

<Steps>
<Step title="Enable the two APIs">
Expand Down Expand Up @@ -62,7 +64,7 @@ companies and applications. Two more things in the Cloud console make that work:

Tokens are stored on your server as Google issues them, the same way MCP connection tokens
are; the database is the trust boundary, and nothing read through a token is ever written
to it. Disconnecting — from Settings, or `disconnect_google` by conversation — revokes the
to it. Disconnecting — from Settings, or `disconnect_account` by conversation — revokes the
token at Google and deletes the row.

## Who it lets in
Expand Down
Loading