Record all three IMAP flags, and say why the index cannot serve a list yet
The migration has run, so is_answered exists and SearchIndexService now writes it alongside is_read and is_starred. The index has recorded two of the three flag...
Do not read or write is_answered before its column exists
Follows the shared-library change unmapping the field. Nothing in the deployed path now touches a column that KamoInitializer has not yet created. The index-ba...
Stop leaking an IMAP IDLE connection on every browser reconnect
Interrupting the IDLE thread cannot end an IDLE: it is parked in a blocking socket read that only sees the flag once the read returns on its own, up to Dovecot'...
The source half of 1b3553f, which committed only its tests
1b3553f staged its two new test files and nothing else, so it landed asserting a nullable FolderManager.setFlags that did not exist yet and cannot compile on it...
Stop an unnamed IMAP flag from being overwritten
IMAP flags are independent, so "mark this read" must say nothing about the star. EmailImapService.setFlags has always taken nullable Booleans and skipped the nu...
Stop shipping a shared credential as a config default
The platform's shared password was baked in as the fallback for DB_PASSWORD and MINIO_ROOT_PASSWORD, so it lived in this repo and in the built image as well as ...
Take the caller from the session, not from the query string
GET /api/calendar/calendars took userId as a request parameter, so changing a number in the URL read anyone's calendars — and because calendars carried no organ...
Make MKCOL create a book that can actually be stored
mkCol built a ContactBook carrying neither an organisation nor an owner and returned 201 regardless, so creating an address book from a CardDAV client failed at...
CockroachDB-only SQL that PostgreSQL rejects
CockroachDB spells the text type STRING; PostgreSQL and YugabyteDB use TEXT. 25 casts across native queries were still emitting CAST(x AS STRING) and ::STRING, ...
Scope address books to the member who owns them
Contacts were scoped by organisation alone. listBooks(orgId) was findByOrganizationId(orgId), and the contact list query opened WHERE c.contactBook.organization...
Publish row ids as strings — a new book 404'd the moment you selected it
CockroachDB unique_rowid() produces 64-bit ids. A real one from contact_books is 1200081912680906758; JavaScript's JSON.parse rounds anything past Number.MAX_SA...
Record message sizes instead of writing zero
SearchIndexService wrote sizeBytes = 0 for every message it ever indexed, under the comment "Not available from envelope". It was available — the fetch profile ...
Restore the files 2b23346 dropped, and answer an oversized send with 413
2b23346 was committed through a private index that was never seeded from HEAD, so its tree held only the three files staged into it and the commit deleted the o...
Stop capping every send at 1MB, so a pasted image can actually go out
Composing with a pasted image failed with a bare "Failed to send email". The composer inlines the paste into the body HTML as a base64 data URI, which rides in ...
Stop Hibernate mangling the "::" casts in the native SQL
Hibernate's native-query parser treats "::" as an escaped colon and forwards a single ":". CockroachDB received **************** ARRAY[]:text[]), ' ') and r...
Keep retrying the indexer's durable NATS consumer after a deploy
A rolling deploy overlaps pods and the outgoing one still holds the durable consumer, so the incoming pod's bind to "email-metadata-indexer" fails with [SUB-900...
Adapt the metadata indexers to array-typed columns
Follows the kamo-shared-library change that maps conversations/search_index array columns as String[]/UUID[] rather than List<T>, which is what stopped every in...
Bound the bulk folder index and stop it on structural failure
Uncommitted work from a concurrent session, swept in so the tree is clean. The bulk index walked pages with totalPages set to Integer.MAX_VALUE and no ceiling,...
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...
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 ...
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...
Stop classifying a multipart/related HTML root as an attachment
Every transactional email we send with an inlined org logo arrived in our own /messages viewer with a correct subject and a completely empty body. isAttachment...
Scan the shared MFA repositories
MfaEnrollmentService is a @Service in kamo-shared-library, so the wildcard @ComponentScan("com.kamo") in this application constructs it whether or not this serv...
Merge duplicate kamo key that crashlooped EmailService
The new EmailService pod has been in CrashLoopBackOff since 16e8a05 — 22 restarts, never once started. snakeyaml raises DuplicateKeyException on a repeated top-...
Harden the kubectl download against flaky egress [skip ci]
dl.k8s.io over the runner's egress intermittently drops mid-transfer: curl: (56) OpenSSL SSL_read: decryption failed or bad record mac which fails the deploy ...
Add missing CampaignController REST endpoints for /api/email/campaigns
The bulk-email workspace/editor call GET/POST /api/email/campaigns etc., but only CampaignService (the @Service) was ever created — no @RestController exposed i...
Remove ambiguous unsubscribe requestMatcher that crashed startup
EmailService mounts two servlets (MVC DispatcherServlet + Spring-WS MessageDispatcherServlet at /services/*), so a bare string requestMatchers( "/api/email/unsu...
Stop serializing computed xoauth2 getter that broke MailCredentials Redis cache
MailCredentials is cached in Redis (GenericJackson2Json). The added `isXoauth2()` boolean getter was serialized as a "xoauth2" property but has no field to read...
Scope KamoMail mailbox/domain listing to the org's own domains (cross-tenant leak)
SECURITY: **************** ran 'SELECT email FROM virtual_users' (and **************** 'SELECT name FROM virtual_domains') with no org scoping, so mailbox/domai...
Persist selected OAuth provider on callback so it stops reverting to KamoMail
Switching an org to Microsoft 365 (or Google Workspace / Zoho) never stuck: the OAuth callback stored tokens and only flipped status on a pre-existing provider ...
Resolve welcome-email logo from the org theme host, not the apex
Header/footer logos in system emails (welcome/verification) were built as **************** — an apex path that only exists on the Kamo marketing site, so for cu...
Constant-time internal-auth secret compare (timing oracle)
Replace String.equals with MessageDigest.isEqual so response latency cannot leak the X-Internal-Auth shared secret byte-by-byte. Fleet-wide sweep of the copy-pa...
Return 422 (not 500) when member has no mailbox
listFolders and other read endpoints threw IllegalStateException from MailContext.toMailCredentials() when a member had no IMAP credentials provisioned, which t...
Don't save transactional NoReply sends to a Sent folder
NoReply is a send-only address with no mailbox, but every transactional send still attempted an IMAP append to its Sent folder, producing a noisy AUTHENTICATION...
Mailbox list API returns UI-shaped ****************
GET /api/email/mailboxes returned the raw entity (providerStatus, lastSyncedAt, no member count). The settings UI reads **************** so status was always un...
Authorize privilege-based mailbox access for read + send-as
resolveForMailbox only granted access via a direct MemberMailbox assignment, so mailboxes surfaced through **************** rights threw NoSuchElementException ...
Preserve original filename and content type on attachment download
The **************** endpoints (both the personal and shared-mailbox controllers) were emitting Content-Disposition: attachment with no filename parameter and C...
Sync members.email when primary mailbox is assigned, changed, or removed
When a mailbox was assigned to a member in a KAMO_MAIL org, members.email was never updated, causing EmailCredentialService to fail to resolve IMAP credentials ...
Mark-as-read for shared and member mailboxes
EmailController.setFlags now accepts an optional mailboxId query param and routes through resolveProviderWithMailbox so member-mailbox flags are set against the...
Handle IMAPInputStream in getMessage to prevent ClassCastException
JavaMail can return an IMAPInputStream instead of a decoded String from part.getContent() when message content has not been pre-fetched. The direct (String) cas...
Use getHasAttachments() to match Boolean wrapper type
**************** is a Boolean (wrapper), so Lombok generates getHasAttachments() not isHasAttachments(). The latter only exists for primitive boolean. Use Boole...
Reset stale hasAttachments on sync and accumulate correctly
- Reset has_attachments=false at start of bulk sync so stale true values from old false-positive MIME detection are cleared before re-indexing - Accumulate ha...
Support fromAddress override on send
Adds FromAddressRequest to EmailSendRequest so the frontend can specify which alias/shared/mailbox address to send from. SmtpOnlyProvider now uses that address ...
Fix attachment parsing and hasAttachment false positives
Three fixes to EmailImapService: 1. toEnvelope: narrow multipart check to multipart/mixed only — plain HTML+text emails (multipart/alternative) no longer sh...
Add com.kamo.z.shared.commerce to EnableJpaRepositories scan
**************** (required by **************** in the shared library) lives under com.kamo.z.shared.commerce but was not included in the JPA repository scan, ca...
Correct field name and type in getFromAddresses response
Backend was sending 'email' key but frontend expected 'address', and sent type 'shared_mailbox' when frontend expected 'shared'. Also fixes hasMailboxes flag wh...
Fall back to org_domains when no email_provider_domains default set
KamoMailDomainValidator was hard-failing with 403 for orgs that have a DNS-verified domain in org_domains but never added it to the email provider domain regist...
Wire up status filter and auto-seed required templates on list
- EmailTemplateController.list() now accepts ?status= param and filters using the existing findByOrgIdAndStatus repository query - Auto-seeds required templat...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.