The KamoMail domain check reads once a minute, and rows off an org's domains leave every list
SP99-T1-f, finished (SP98 Task 8). SP99's final review already refuses a KamoMail binding whose address is not on a domain the organization holds (95c58af); thi...
An AI organiser's invitations carry the AI disclosure its email does
Event invitations wear no compose template, so an invitation, update or cancellation organised by an AI member carried none of the disclosure its email carries ...
A late delivery still raises NEW_EMAIL and buzzes the phone; only IDLE is judged by age
NewMailAnnouncer's 30-minute Date-header gate, there for IMAP IDLE reconnect replays, also dropped Dovecot push arrivals, which report a delivery (MessageNew) a...
A From's mailbox id picks a signature only when the caller may send as it
The compose identity took fromAddress.id at its word: E1's send and E4's render check the From's address and the sending mailbox, never the id, so a member coul...
The groupware provider finds a series by its UID as its master
SP98-F-3. CalendarService.findEventByUid read "the one row with this UID" in a calendar, and an edited instance of a series is a row of its own under the series...
An inbound invitation answers with its summary, as the account that received it
SP98-F-5. **************** returned the CalendarEvent entity, whose attendees point back at their event, so writing it recursed until the stack overflowed; it n...
Address books are found, and reached only by their owner with the web's rights
SP98-F-4. CardDAV wrote resourcetype as empty text for the home, every book and every card, so no client ever found an address book; a book is now *************...
A calendar edit changes only what it sends; a new one without a zone takes the member's
The calendar create and edit bound the UserCalendar entity, whose defaults (zone UTC, visible, sort order 0) stood in for every field a body left out. KamoMobil...
An alias goes only to a member of the caller's own organization
POST /api/email/aliases took any memberId: an administrator could create an alias in their organization that forwards to another tenant's member, and read that ...
Only an open god-mode window bypasses a tenant right, never GD alone
Every EmailService rights check let a session with GD through ("GD passes everything"), but GD is god-mode ELIGIBILITY, permanently true for the operators who h...
KamoMail binds only an address on a domain the organization holds; the provider needs its right
The cross-tenant read path parked as SP99-T1-f. A self-registered organization could point its provider at its own IceWarp or Exchange (PUT /api/email/provider ...
Indexing a listed page can no longer fail the listing
recordSyncedPage was @Transactional and caught every exception of its own. The index repositories join the caller's transaction, so a write that met a concurren...
Basic sign-in asks SecurityService's real credential check
DavAuthFilter posted Basic credentials to **************** which never existed (405), so every username-and-password sign-in from iOS or DAVx5 was answered 401 ...
A mail domain, and every account on it, belongs to the organization that proved it
The KamoMail server's virtual_domains and virtual_users are shared by every tenant, and nothing stopped one tenant from writing to another's part of them: - PO...
An organization's NoReply domain is chosen by name, not by row order
The campaign From, the transactional Reply-To and the provider default domain each took "the first root org_domains row". Organization.getDomains() is unordered...
A meeting invite answers with a summary, not the entity graph
Sending or updating a meeting invite answered with the CalendarEvent entity. Its attendees point back at their event, so Jackson recursed until the stack overfl...
A composer's meeting invite goes into the organizer's own calendar
MeetingInviteController handed MeetingInviteService.sendInvite the session's member id in the user-id slot, and sendInvite looked the calendar up with findByUse...
A signature's icons do not put a paperclip on the message
"kamo Marketing teaser" listed with a paperclip and opened with nothing to download. It has no file: the two parts it carries are the 14px phone and email icons...
A calendar message's plain-text part says what its HTML says
The text/plain alternative of every calendar message was "You've been invited to: " + subject, so an instance cancellation read "You've been invited to: Cancell...
Let the WebDAV verbs through Spring Security's firewall
StrictHttpFirewall admits only GET, HEAD, POST, PUT, PATCH, DELETE and OPTIONS, and answered every PROPFIND, PROPPATCH, REPORT, MKCOL and MKCALENDAR to /dav/** ...
A moved series keeps its days, its end and its exceptions; a lead callback never repeats
A whole-series move (ALL, or THIS_AND_FOLLOWING) now carries the rule with it (spec D-9): UNTIL moves by the same local step, so the last instance stays; a week...
CalDAV reads are authorised, bodies are generated, and a phone's PUT is parsed
Calendars are listed as calendar collections of events, resources are named by the UID their calendar-data states (encoded in hrefs), writes pass the read gate,...
The email.* host no longer reaches EmailService's server-to-server endpoints
The email-route IngressRoute sends every path on an email.* host straight to EmailService, past the API gateway, so the gateway's carve-outs (master spec sectio...
An organization with no domain still sends its letters
A domain is optional when an organization is created, but every transactional letter such an org sent threw "has no domains; cannot build NoReply from-address" ...
Ics import and export work — rows in, rows out, series included
Import upserts each UID group (master, canonical rule, EXDATEs, edited and cancelled instances) with no reminders and no invitations; E11 reports {created, upda...
Pin kamo-shared-library 1.6.1, the version the library stays at
39829f6 pinned 1.6.2 together with the library bump in db737a54. That bump is reverted: about thirty other services pin 1.6.1, and CI can only resolve the one v...
An attachment with a Content-ID is still an attachment
A PDF sent from the Gmail app had a paperclip on its list row and no download link in the reading pane. The app stamps every attachment with a Content-ID, and t...
Accept a short-lived otk on the WebSocket handshake, alongside ***
Same change as mediaservice: kamo-internal is moving the *** session id out of browser JavaScript, off sessionStorage and off every WebSocket URL (`?***=<128-he...
Catch dead domains and dead mailboxes the checks let through
Campaign 8781b848 ended with 667 bounces and 1,258 failures. Three gaps in the existing eligibility engine let them through: - DomainMailCheck asked A and AAAA...
An IMAP/SMTP connection test must not be usable as a network probe
POST /api/email/member-imap/test and /{id}/imap/test connected to whatever host and port the caller supplied and returned the raw exception message. Any authent...
Alias, domain and personal-mailbox admin actions need a management right
create/delete on an email alias, **************** on the org's sending domains, and **************** on a personal (self-hosted) mailbox all needed only a sessi...
A member's mailboxes and aliases are only readable inside their own organization
GET **************** took no HttpServletRequest at all — no session, no org check anywhere on the path — and GET **************** checked that the CALLER had a ...
A calendar export or import must be one the caller can actually open
GET /export/calendar/{id} and POST /import/{calendarId} took the calendar's UUID alone — no HttpServletRequest, so no session, no org or member check at all. An...
SearchService marks its injection constructor, so the service starts
9809d4b gave SearchService a second constructor (a clock, for tests) and marked neither, so Spring chose neither, looked for a no-argument constructor, and the ...
Search that honours its query, and every mail action on the mailbox that is open
Two problems on /messages, fixed in the service. Search did not work. The index path threw a ClassCastException on every hit (Instant -> Timestamp, visible in ...
Shared-mailbox **************** need a management right
**************** on /api/email/shared-mailboxes needed only a session — any authenticated member could create a shared mailbox, delete one, or grant themselves ...
An OAuth state token is a server-side record, not a blob the browser hands back
/api/email/oauth/callback and **************** are public and sessionless (the provider redirects the browser here with no Kamo session), so `state` was the onl...
A From override must be an address the caller may send as
POST /api/email/send took request.getFromAddress() straight into the header From and the SMTP envelope sender **************** -> EmailSmtpService.send, and the...
Aliases and shared-mailbox access name their members as text
GET and POST /api/email/aliases (and /aliases/member/{id}) and GET and POST **************** answered with the entities themselves, whose Long memberId and gran...
Saving a calendar/contacts integration never blanks or drops its stored credentials
A contact integration's credentialsJson was replaced by whatever a save sent. The org CalDAV/CardDAV tab sends all four server fields (empty — they never show w...
A shared mailbox's access list is only readable inside its own organization
GET **************** called **************** which read the access rows for any mailbox id. Every other access operation (update, delete, grant, revoke) loads t...
A person taken out of a drip stays out
The engine moves on up to 200 due people per drip from copies it read at the start of a pass, up to 25 seconds old by the time each is used. Meanwhile a member ...
A drip's People counts follow the search, so All is always the sum of its chips
Stop refreshing a mailbox grant no app can refresh, and say it needs reconnecting
An org connected Microsoft 365 through Kamo's platform app; the platform app was later switched off and the org has no registration of its own. The resolver the...
Every refused request says why, not just campaign calls
2a72597 gave the campaign controllers the member's sentence instead of "Bad Request". The same loss happened everywhere else in EmailService that refuses with a...
A refused campaign call says why, instead of "Bad Request"
CampaignService refuses with a ResponseStatusException carrying a sentence written for the member ("Pause the campaign before deleting it.", "Only paused campai...
Accept OCI image indexes when resolving the built digest [skip ci]
The images are pushed as OCI image indexes, so asking the registry for a single image manifest only answered 404, the digest came back empty and the check faile...
Restart when a same-commit rebuild leaves pods on the old digest [skip ci]
The rollout step tried to detect a same-commit rebuild by comparing the Deployment's image reference before and after `set image`. "Apply manifests" has already...
Saving a campaign no longer fails with "Restart read required"
The bulk-provider row (hosts, send window, warm-ups, campaign audiences) was read inside callers' longer transactions. A campaign save writes the row's audience...
The organization owner can open the opt-out list
A brand-new right is granted to nobody on the day it deploys, so a list gated only on MANAGE_EMAIL_OPT_OUTS would ship visible to no organization. The owner hol...
Like what you see shipping?
All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.
