Purge lead e-mail links whose lead-side address is the company's
Clears what the old company-address rule wrote before it read org_domains. Live production was purged by hand at the time (126 rows, 6 leads, one org); this exi...
Never match a lead against the company's own addresses
A lead whose e-mail was set to noreply@kamocrm.com matched every message that address had ever sent — one lead record surfaced the company's entire outbound mai...
Drop the description line above the Resources document manager
It described a tab that now shows its own contents, so it was only taking space. descriptionKey goes optional rather than staying as an unrendered string: a tab...
Mirror Maven Central and retry, so a corrupt transfer stops killing the build
The HR_RESOURCE rebuild failed on hibernate-core:6.5.3.Final with 'Tag mismatch' — a transfer corrupted in flight, not a missing artifact. The shared-lib stage ...
Stop double-appending the query string on the communications route
All / Calls / Texts / Emails failed with "We couldn't load this lead's communications". The backend was fine — direct calls returned 200 with real entries. The ...
Resources tab is the org's HR document library
Replaces the Resources placeholder with DocManager on a document collection of its own — ImageAssocType.HR_RESOURCE, bucket imaging-hr-resources — org-scoped th...
Rebuild for ImageAssocType.HR_RESOURCE
ImagingUploadController reads ImageAssocType.values(); without this rebuild an upload from the HR Resources tab cannot resolve ordinal 13 to its bucket.
Rebuild for ImageAssocType.HR_RESOURCE
**************** resolve the assoc type with values()[ordinal]; without this rebuild values()[13] throws on the stale 13-constant array as soon as the HR Resour...
HR_RESOURCE assoc type for the org HR resource library
The /hr Resources tab publishes handbooks, policies, forms and reference material to the whole team, and no existing assoc type fits: MY_DOCS is a person's own ...
Remove the god-mode time-box
Checking in work left uncommitted in this working tree by a concurrent session. Not authored here; committed so it is not lost and so main is not left with a tr...
Timeline a11y, timezone-correct day headings, and a summary card that isn't zero
Three defects, each invisible until it bites. The row expander wrapped the whole header in a <button>, and DateTimeDisplay renders its own interactive tooltip ...
Real chat direction, and stop the timeline mapper writing or fanning out
Chat entries claimed no direction and attributed every message to its row's author. Social conversations record BOTH sides against a Kamo Member — the visitor's...
Stop re-querying call history per extension when the provider is instance-wide
RingCentral's endpoint is extension/~ and ignores the argument, so it returns the same log for every extension. Once a page adds nothing new, the remaining exte...
Pin the contact-point and linker invariants, and drop timeline count queries
The two classes the whole feature rests on had no tests, and all three properties they guarantee fail silently in production: an unmatched call is indistinguish...
Human Resources module shell with five route-backed tabs
Adds /hr with the accounts-page chrome in its route-backed form (the same one /marketing already uses): a layout owning the gradient hero and underlined tab bar...
Isolate call ingest per row and link only after it commits
Two faults with one root: ingest participated in the sweep's transaction. A throw inside a participating transaction marks the SHARED transaction rollback-only...
Make communication source ids natural keys, not row ids
A source id derived from the source row's generated UUID is only stable if that row commits. The VOIP ingest paths write inside the sync transaction while LeadC...
State honestly why chat entries carry no direction
The javadoc claimed direction was derived from the author while the code hardcoded UNKNOWN. Social conversations record both sides against a Kamo Member — the v...
Skip soft-deleted extensions in the call-history sweep
syncExtensions soft-deletes by flipping status to UNKNOWN rather than removing the row, so the list accumulates every extension an instance has ever had. Each o...
Page the lead sweep until the window is covered
A single fixed page silently lost mail whenever a mailbox took more than pageSize messages between ticks — a newsletter burst, a busy shared inbox, or just the ...
Complete the Communications tab
Each sub-tab now holds its own data, and All interleaves every channel by date. Notes keeps MediaStream, because notes are authored here — but readOnlyChrome s...
Unified communications timeline API
GET **************** returns one date-ordered page across notes, calls, voicemails, texts, e-mails and chat; ?channel= narrows it. Three sources are merged. Ca...
Drive the mail-to-lead association sweep
Five-minute tick against EmailService's internal sweep endpoint, mirroring VoipGlobalSyncJob. The schedule lives here because EmailService runs several pods and...
Associate lead correspondence, and fix the dead push indexer
Adds the mail half of the lead Communications tab, plus the ingestion it needed to exist at all. The platform has no working mail push: IMAP IDLE is hardcoded ...
Persist calls and associate calls, texts and voicemail with leads
Calls were never recorded anywhere — VoipCallEventService published a NATS event for the browser and kept nothing, and RECORDINGS only exists when audio was cap...
Schema and contact-point seed for lead communications
Hibernate creates the three new tables from the shared-lib entities; this adds what it cannot express — the partial unique index that keeps at most one open con...
Point-in-time contact-point index and lead communication links
Adds the spine the lead Communications tab reads from. LEAD_CONTACT_POINTS records every phone number and e-mail a lead has ever had, as a window with activeFr...
Submit the lead at step 1 instead of only on booking
handleFormSubmit checked the *** and advanced the step — it made no network call at all, so the contact details a visitor typed were only ever persisted if they...
Capture the demo lead before the booking step
The marketing site's demo form collected name, email, company, company size, industry and a message, then advanced to scheduling without telling anyone. Lead cr...
Public demo-request lead capture primitives
A visitor who fills in the marketing site's demo form but never books a time left no trace: the lead was only ever written inside the booking transaction. Captu...
Withhold the reply box from senders that cannot receive one
Sending a quick reply to a KamoCRM notification put the literal text `{"error":"Invalid Addresses"}` in the reply box. The mail server had rejected NoReply@Kamo...
Address replies at Reply-To, and stop leaking SMTP exceptions to the UI
A quick reply to a KamoCRM notification failed with `{"error":"Invalid Addresses"}` rendered verbatim in the reply box. That string is Jakarta Mail's own wordin...
Register Provider Setup in navigation, and write the quick tips
The Provider Setup tab rendered inside SettingsView but was reachable only by typing the URL — it was missing from both places that list settings destinations: ...
Drop the now-dead mailbox parameter and local
Follow-up to c7cb180: once the mailbox left the voicemail id, toVoicemailEntry's mailbox argument and the graphReady local had no remaining readers.
Outbound Teams call party, live audio devices, provider-aware prompt
Three confirmed findings from the review of 08cecc6d. Outbound Teams calls showed the wrong remote party. TeamsAcsSession derived remoteNumber/remoteName from ...
Close a cross-mailbox hole and six more Teams defects
Adversarial review of 6df0008 confirmed 24 findings. The important ones: CRITICAL — any member could delete a colleague's mail. Teams voicemail ids were compos...
Enforce geo and IP access rules on page loads
checkAccess evaluated the rules itself and matched nothing for its entire life: it compared rule types against **************** while the publisher writes the e...
Enforce tenant access rules at the gateway
This filter has been inert since it was written. resolveOrgId read org:domain:<host> from Redis, nothing ever wrote that key, and the null return fell through t...
Actually enforce geo and IP access rules
The rules were stored, cached to Redis and never consulted. AccessCheckController was the only caller of the evaluation and nothing called AccessCheckController...
Platform registration card for the Zoom Marketplace app
Completes Zoom's one-click connect path. The backend already resolves PlatformOAuthProviderType.ZOOM, but with no card here a platform operator had nowhere to e...
Microsoft Teams Phone provider, front to back
Adds Teams to **************** and wires the whole path through: provider selection, the Entra + Azure Communication Services config, tenant admin consent, the ...
Microsoft Teams Phone as a provider
Teams is not one API, so the provider glues three control planes together: • Graph, app-only — call history (getPstnCalls), presence reads, directory and...
Provider Setup tab and capability-gated meeting settings
Adds the Provider Setup tab to /settings/features/meets and makes every other tab honest about the provider it is configuring. useMeeting no longer builds meet...
MICROSOFT_TEAMS provider type
Appends MICROSOFT_TEAMS to VoipProviderType. The enum persists as ORDINAL on **************** so the position is load-bearing and the constant goes last; the cl...
Route the meeting provider OAuth callback
Zoom and Microsoft redirect the browser to this gateway after an org authorises Kamo's app. The popup lands here with no tenant session — the org travels in ?st...
Meeting provider abstraction with Zoom and Microsoft Teams
Meetings were Kamo Meets by construction — MeetService persisted a Meet room name and nothing else. This introduces the abstraction and two remote providers beh...
Schema for the multi-provider meeting layer
Creates **************** and org_meeting_oauth_tokens, adds the provider-identity columns to MEETING_RECORDS and MEETING_RECORDINGS and the Zoom/Teams-capable s...
Model meetings as provider-agnostic
Kamo Meets was the only possible backend: OrgMeetConfig was a flat Meet settings bag and a MeetingRecord held nothing but a room name. This adds the schema for ...
Fit the trail in one pane — no wrapped intro, no double scrollbar
Three things made the tab feel cramped: The intro was capped at 900px, which broke a 170-character sentence onto a second line for no reason; it now uses the w...
Allow-list countries on the geo-blocking card
The card could only deny countries, so an org that serves one country had to enumerate every other one. It now has both lists: a mode toggle picks which list th...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.