A NUL byte in two source files, which made git call them binary
Caught on a last pass over my own work: `billingDraft.ts` committed as "Bin 0 -> 6882 bytes". A separator written as a space had reached the file as a raw NUL b...
Watch a mailbox for the phone, not only for the browser
An IMAP IDLE listener is the only thing that notices a message arriving; it publishes email.inbox.<addr>, which is what eventually asks MediaService to buzz a p...
Stop the presence gate silencing every mail notification
A member with a live MediaService socket was treated as having already seen every non-call push, mail included. That is true of what travels on that socket — a ...
Pay without leaving the order, and never lose it again
Sending somebody to another page to add a card was the wrong answer, and it was wrong at the worst possible moment. The console noticed there was no card and li...
The app shell was capping every page under every scrollbar
My last fix was in the wrong file. Removing the stacking context from the launchpad changed nothing, because the one that matters is a level above it, in the sh...
Ask whether THIS NUMBER can text, not whether the org can
SmsGateway grew a number-aware availability() when routing moved onto the number, and then nothing called it with a number — both SmsController call sites used ...
The commitment term travels with the plan, not just the plan
Carrying the plan code alone was half a fix, and the missing half was the half that carries the price. Somebody who set three years on /myplan, saw $49 a seat a...
Opening an attachment no longer throws React #310
Clicking a thumbnail threw "Minified React error #310 — rendered more hooks than during the previous render" over the whole page. Mine, from the attachment-aut...
Finish the spec's inventory, and add the order to check things in
The "where it lives" table was written before the setup service, the media handoff, the CARRIER_TO_PHONE_SERVER rail and the phone server's default text route e...
Delete the Telnyx code nothing calls
Three methods I added that no caller reaches, removed while the reason is still fresh — this feature's worst bug was TelnyxProvider.provision() being defined an...
The "you cannot text" message stopped one step short of the fix
FreePBX and Teams told a member to add an SMS provider under Phone → Providers. That was complete advice until numbers gained their own text route; now adding a...
The two inbound shapes, and the rule that keeps the endpoint safe
Records what voipservice 9e9e814 and ea6d631 established, because the reasoning is not recoverable from the diff. A Telnyx messaging profile has exactly one we...
Only the carrier that owns a webhook URL may verify its posts
The phone-server inbound path added in the previous commit verified against whatever provider carried the number. That is a forgery surface, and not hypothetica...
A Telnyx phone server could send texts and never receive one
Telnyx is both a phone server and a carrier, and a Telnyx messaging profile has exactly one webhook URL. So an organization that added Telnyx as a PHONE SERVER ...
One setting that covers every number on a phone system
The approved routing design had three tiers and only two of them were reachable. A number could name its own text provider, and failing that the phone server wa...
The /myplan to console journey, end to end
The preselect landed across two sessions and two files, so the thing worth pinning is the journey rather than either half: a plan chosen on /myplan arrives sele...
One price on the screen, and a purchase that cannot fail silently
The console showed a customer three different numbers for one plan and let them buy with no card. Rendering it against the live catalogue, Business at five seat...
React to a status change, not only to a comment
"🎉" on a Fixed event is the single most natural thing anybody wants to say in a bug thread, and offering reactions only on comments meant typing one to say it....
What the thread design got wrong, as built
Two schema faults that only appeared on deploy — the hand-applied column that was flagged and not applied, and a frozen ddl-auto CHECK on media_sessions that wo...
Live thread updates, and reactions that are actually reachable
Two halves of the thread were built and then left unconnected. The server has published every comment and status event to `chat.session.{guid}` since the featu...
The thread page names its reader, and drops an unused helper
`viewerMemberId` on the thread page, so the console can tell the viewer's own reactions from everybody else's without a second call to /api/user-info. A ref fil...
Actually create the Telnyx account setup, carry MMS, and let a carrier-only org hold threads
Three gaps, and the first was the feature not working at all. TELNYX PROVISIONING WAS DEFINED AND CALLED BY NOTHING TelnyxProvider.provision(), ensureMemberCr...
Stop the launchpad capping its own dropdowns under every scrollbar
The admin bar's dropdowns were painting under the App Shelf's scrollbar, and lowering the shelf's z-index last time did not help — because the number was never ...
A text conversation can exist without a phone server
VOIP_CONVERSATIONS.INSTANCE_ID was NOT NULL, which quietly decided that every text thread belongs to a PBX. An organization that buys numbers and messaging from...
The plan you picked travels to the page that sells it
"Move to Business" opened the billing console with nothing selected. The choice the person had just made was dropped on the floor and they were asked to make it...
Charge what the screen says, and refuse rather than pretend
An audit of the Plans & Billing console against live Stripe keys found the seam between the screen and Stripe broken in every direction at once, and silent in a...
The two letters a customer gets when their card fails
BillingService has wanted to send these since DunningNotifier was written. It posted them to **************** — an endpoint that has never existed on EmailServi...
Pin KamoCRM's own sales details, and that no org can change them
The address and number are constants a single edit away from becoming a tenant lookup, which would route a customer asking about a KamoCRM plan back to their ow...
Populate a new number before persisting it, not after
save() built an empty OrgPhoneNumber, persisted it, and then filled in E164 and PHONE_CANON — both NOT NULL. It works today because the UUID generator needs no ...
A plan card says what it adds to the one below it
The picker gave a price, a seat ceiling and one sentence. Choosing between $46 and $80 a seat on that is a decision with one obvious answer, and for most teams ...
Attachments authenticate like chat's, instead of showing broken
Every attachment on a bug report rendered as a broken tile. In production: seven content requests, seven 401s, zero successes — while the report's own JSON load...
One way to reach sales, and a seat control that knows the plan's limit
ContactSalesDialog holds KamoCRM Inc.'s own details — Sales@KamoCRM.com and (949) 298-9960 x1 — as constants, deliberately. Every other address on this platform...
A Numbers screen, so calls and texts can come from different companies
An organization buys its numbers from one company and answers the calls with another. That is ordinary, and Kamo could not describe it: texting was a property o...
Anchor it to the first cell, and stop the scrollbar collapsing it
Three things, and two of them are the same mistake: asking a question about a portalled element as though it were a child. THE SCROLLBAR COLLAPSED THE PANE. Sc...
Route texts by NUMBER, and add Telnyx on both sides of it
An organization can buy its DIDs and its texting from one company and answer the calls with another. Kamo could not express that: a member's texting was a prope...
One source of truth, and a build that can actually reach it
Every price on this site had a second copy in the repo, and the second copy was the one being served. kamo-prices.ts said so in its own comment: the fallback t...
Telnyx, the first provider that is both a phone server and a carrier
Telnyx sells SIP trunking, DIDs and messaging, and an organization can buy those halves independently. The case this is for: DIDs and texts bought from Telnyx, ...
Drop the frozen type_id CHECK that blocked every bug thread
`media_sessions` is created by Hibernate's ddl-auto, not by a migration, so it carries the constraint ddl-auto writes for an `@Enumerated(ORDINAL)` column — fro...
The shelf replaces the per-app cards, and four fixes to its rows
The launchpad was a wall of one card per app. The App Shelf lists every one of those apps in a single pane, with what each does and everything inside it, so the...
A plan's startup fee, and the live offer, on the wire
PlanDto carried only the recurring rate, so every surface quoting a plan quoted a price the customer would not be charged: KamoCRM's startup fee is $99 a seat, ...
Quote the real price, and stop the Free plan looking like the best one
Two things on this page were wrong in a way that reaches customers. The price. bestRate() took the cheapest rate across all three terms and printed it as "per ...
The templates button called an endpoint that could not answer
Found by reading the controller rather than the code calling it, and it was broken twice over in a way that LOOKED fine: - The list asked for `status=ACTIVE`...
The report's thread in the drawer, shelving, and Issue Persists
`SystemBugThread` renders everything anybody has said about a report and every status it has passed through, under the current-status callout: the callout answe...
Preview variables for SYSTEM_BUG_COMMENT
Without them the template editor renders the new letter with holes where the report number, the sender and the button go, which reads as a broken template rathe...
System_bug.media_session_guid, and its lookup index
The column behind a report's comment thread, matching **************** — VARCHAR(36), because MEDIA_SESSIONS.GUID is itself a 36-character string column and a t...
Send To AI tells everybody on the thread, not just the reporter
The dispatch sends the same letter MediaService does, so its audience has to move the same way: every participant in the report's thread minus whoever acted, ea...
Two rows tall, and it opens by widening rather than by a second pane
The pane was 861px tall, not the 496 its two rows ask for. The launchpad grid is gridAutoRows: minmax(106px, auto), and an auto maximum sizes a track to its ite...
The thread behind a report, and the two statuses that need one
`SystemBugThreadService` is the whole feature: a report's MediaSession, the status events written into it with the reason their author gave, the comments anybod...
Shelving, failed fixes, and a report's whole comment history
A report kept exactly one sentence. `system_bug.denial_reason` was overwritten by every subsequent status move, so a bug that was denied, explained, reopened, f...
No explanation that lasts one frame, and a failure you can act on
A refused member was shown an info alert and redirected home in the same breath, so the message appeared for one frame and was replaced by the launchpad. That i...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.