feat(api): add zoho sign-in and zoho mail mailbox sync - #211
Draft
WifiDan wants to merge 2 commits into
Draft
Conversation
|
@WifiDan is attempting to deploy a commit to the Comp AI - PoC Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opening this as a draft, and leading with the why as
CONTRIBUTING.mdasks. There is amatching ADR in the diff at
adrs/zoho-mail.md— that file is the argument, this is thesummary.
Why
We run this CRM against a business whose mail is on Zoho, not Google Workspace or
Microsoft 365. Today that means the agent has nothing to read: the sales mailbox is on
Zoho, and the only mailbox the CRM can see belongs to a personal Gmail that isn't where
the business happens. Every company, contact and thread the product is meant to fill in
by itself gets typed in by hand instead.
Zoho Mail is the usual answer for a small company that wanted its own domain without
paying per seat, so this reads less like "support my setup" and more like the
second-tier mail host the CRM's premise quietly excludes.
We looked at generic IMAP and at pushing mail in from a side script first. Both are
argued and rejected in the ADR — short version: IMAP would be the only provider with no
OAuth story and no usable incremental cursor, and a side script leaves the mail outside
everything that makes the mailbox layer worth having (scope checks, purge, reconnect,
the connection card).
What this does
Follows #73 as closely as it deserves to be followed. The provider union in
packages/auth/src/scopes.tsand thesatisfies Record<…>maps inmailbox.constants.tsare already the shape a third provider slots into — the compilernames every arm that needs filling, which is the property you want here. Every one of
those sites is in this diff.
Reused untouched:
ThreadWriterService,MailboxMatchService,SyncStateService, andthe
EmailThread/EmailMessagemodels.zohoMessageIdandzohoWebLinksit besidethe Gmail and Outlook columns in one migration.
Four things about Zoho are genuinely not Graph, and they are where the code stops being
a copy of the Outlook adapter:
the same
genericOAuthplugin Slack already uses. Its callback is/api/auth/oauth2/callback/zoho— note the extra segment. This also means it linksonto an existing account rather than only being a sign-in, which is the case we
actually have: sign in with Google, attach a Zoho mailbox on another domain.
Authorization: Zoho-oauthtoken <token>, notBearer.MailboxApiClientgrewone optional scheme argument; every existing caller is unchanged.
/messages/viewpages withstart/limitovera date-sorted list, so the sync reads newest-first and stops at the first message the
last tick already saw. The cursor is epoch milliseconds rather than an ISO string.
Message-ID. Without one, the same mail seenthrough Gmail and through Zoho would be stored twice, so each new message costs a
second call to
/header. That is the main cost of this adapter and why its per-tickceiling is 60 rather than Outlook's 120.
One refactor rode along, and I'd rather flag it than have it found:
rebuildThreadswasduplicated verbatim in the Google and Microsoft connection services. Instead of adding a
third copy it moved to
mailbox/thread-rebuild.tsand both existing callers import it.Happy to drop that out if you'd rather it stayed local.
What it costs an install that never sets
ZOHO_CLIENT_IDNothing. The provider isn't registered, the connection card says it isn't configured,
and no new query runs.
Tests
bun run check-types,lint,lint:slopandtestare all green. New:apps/api/test/zoho-mail-client.spec.ts(envelope unwrapping, the auth scheme, errormapping, the schema refusing a shape it can't read) and
apps/api/test/zoho-sync.spec.ts(cursor behaviour, folder exclusion, paging by messagecount, threading and dedup), plus Zoho arms on the existing scope and grant specs.
Two things the tests turned up that are worth knowing:
messageIdon the/headerand/contentresponses as a bare JSONnumber past
Number.MAX_SAFE_INTEGER—JSON.parsehas already rounded it beforeany parser can see it. Those two schemas deliberately do not read it; the list
endpoint returns the same id as a string and that is the copy everything uses. There's
a test pinning that.
access_type=offline+prompt=consenton the authorization URL. A connection thatcomes back without one can't be repaired in place, so the card asks for a disconnect
and reconnect and says why.
Status: draft, and what's not done
Not yet exercised against a live Zoho tenant. The OAuth client for our domain needs
Zoho admin access we're in the middle of getting, so every call in here is written
against the published Zoho Mail API docs and covered by tests, not by a real round trip.
I'd rather say that plainly than let it read as verified. I'll take it out of draft once
it has actually synced a mailbox; happy to take review before then.
Summary by cubic
Adds Zoho Mail as a third mailbox provider alongside Google and Microsoft, so installs whose mail is on Zoho can sync email threads instead of typing them by hand. Also adds Zoho as a sign-in option. Installs that never set
ZOHO_CLIENT_IDsee no change.What changed
/api/auth/oauth2/callback/zohoand it links onto an existing account instead of only signing in.MailboxApiClientgained an optional auth scheme; Zoho sendsZoho-oauthtokeninstead ofBearer, and every existing caller is unchanged.Message-ID, so each new message costs one/headercall to avoid storing the same mail twice when it is also seen through Gmail or Outlook.rebuildThreadshelper was duplicated in the Google and Microsoft connection services; it now lives inmailbox/thread-rebuild.tsand both import it.adrs/zoho-mail.mddocuments why generic IMAP and a side script were rejected.Adoption notes
ZOHO_CLIENT_ID,ZOHO_CLIENT_SECRET, andZOHO_REGION; set all three or none. Without them, the provider is not registered and the connection card says it isn't configured.zohoMessageIdandzohoWebLinktoemailMessage.Number.MAX_SAFE_INTEGER, so the/headerand/contentschemas deliberately ignore them and use the string copy from the list endpoint.prompt=consent; a connection that comes back without one must be disconnected and reconnected.Written for commit 66ecc63. Summary will update on new commits.