A campaign can go to every lead, account or application a member can see
"All leads / accounts / applications" is a rule, not a list. It is stored with the campaign (the bulk-provider blob, campaignAudiences β no DDL), runs on the ac...
The preference page in every language the product speaks
The page behind every unsubscribe link only spoke English. It now speaks all 22 languages the product UI ships: English, Chinese, Hindi, Spanish, French, Arabic...
An unsubscribe by email is honoured, not dropped
Every bulk and shared-article message offered unsubscribe@<sender's domain> in its List-Unsubscribe header, but nothing read that mailbox - for a tenant it was ...
Opt-out screens refresh live when anyone changes the list
The ledger now tells OptOutChangePublisher after every committed change - a recipient on the preference page, a bounce, a member in Settings - and it publishes ...
A real preference page behind every unsubscribe link
The unsubscribe link opened a bare confirm-or-leave card. Someone who only wanted fewer emails had no way to say so, and nothing told them why they were being m...
An org admin can see and change the whole opt-out list
Settings -> Email -> Opt Out now has its API under /api/email/opt-outs, behind MANAGE_EMAIL_OPT_OUTS (god bypasses): a summary with the last 30 days, the list f...
Every suppression check and bounce goes through the opt-out ledger
SuppressionService now asks EmailOptOutLedger, so a check sees the do-not-email list, pauses and topics alike, and a bounce lands in the same history as an unsu...
Several outbound hosts per org, sent round-robin, each warmed on its own, inside a send window
An organization's campaign mail now leaves through any number of outbound hosts, one message at a time in rotation, instead of one bulk provider. - Hosts: an o...
The NoReply ledger can be filtered by subsystem, pruned, and told what to record
Each row now names the subsystem that sent it and, for a template send, the template key. EmailTemplateService and the campaign sender open a NoReplyOutboundSou...
A warm-up day nothing was sent on does not count
A warm-up day used to be the N-th 24 hours from the start whether or not anything went, so turning the warm-up on days before the first campaign silently spent ...
Campaign mail the platform relay would refuse fails at once instead of being retried
The relay sends only as its own domain and answers anything else with a 451, so a campaign whose From is on another domain used to retry every recipient three t...
New host warm-up holds an org's campaign mail and releases it at the warm-up's pace
While an org's warm-up is on, the dispatcher leaves its campaigns alone and a releaser sends them one message at a time, the earliest due first across the org's...
Every campaign message names its recipient row in X-Kamo-Ref
The bulk relay logs the header against its queue id and strips it before delivery, so delivery results can be matched to the exact recipient row. The composer'...
The relay's delivery reports bounce, block and count campaign mail after hand-off
POST **************** takes the platform relay's reports of what became of each message, matched to the recipient row by the X-Kamo-Ref header or, failing that,...
The warm-up's schedule, pacing and health rules
A new sending IP is ramped over fourteen warm-up days. Days 1-7 follow a fixed schedule per pace (conservative, balanced, aggressive); from day 8 each day grows...
A campaign cannot be sent or scheduled without a bulk email sender
The dispatcher used to accept such a campaign and then skip it on every tick, leaving it at SENDING with nothing but a log line. sendNow and schedule now refuse...
The provider settings api answers with the saved state and names the field it refuses
PUT now returns the full settings state instead of {success: true}, which blanked the form after every save. Fields are validated server-side and refused as 400...
A campaign emails an address at most once, ever
Addresses are matched trimmed and case-insensitively, and a row the campaign has handed to the relay (sent, in flight, bounced, failed, or retried) is now its p...
A ledger of every message sent as the platform NoReply address, readable by god only
Recorded in EmailSmtpService at the one place mail reaches the relay, and decided by the From address, so transactional templates, campaigns and article shares ...
Email an article to someone, as the platform org, with a working opt-out
A visitor on the marketing site can send the seat-fee article to up to three people. POST /api/internal/article-share (X-Internal-Auth, like every other /api/in...
A guest a service put on an event is actually told about it
InternalCalendarService wrote the attendee rows and stopped. It never set sendInvites and never called EventInviteService, so a guest arriving through the servi...
Let a browser report the two conditions only it can see
Every notification so far is raised by the service that watched something happen. These two cannot be: whether a member looked at a conversation within five sec...
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...
Let a member connect their OWN calendars and contacts
The personal calendars panel asked for GET **************** which was never mapped -- so it failed on every open -- and for Google and Microsoft it had no way t...
Give every provider's folder tree the same shape before any client sees it
Reporting a folder's SPECIAL-USE attribute was not enough, because a server only sends one if it is configured to β and the KamoMail Dovecot in production sends...
Connect Google and Microsoft calendars over OAuth, on the app the mailbox already uses
The CalDAV/CardDAV tab's Connect via OAuth button raised an alert saying the flow was not implemented and to configure credentials through the API. It now runs ...
Send an organization's mail as its own domain when its SPF says we may
The From address was chosen by asking who hosts the organization's MAILBOXES. That is a different question from the one that matters, and it was wrong in both d...
Send an org's own hostname as the mailbox OAuth redirect
An organization that brought its own Google or Entra app registered that app in its own account, under its own brand, and is now told on its settings screen to ...
Lock the shared sweeps, and run two pods
Nine of EmailService's ten scheduled sweeps have a shared effect and now take a named lease before running. The most consequential is CampaignDispatcher: at two...
Buzz a member's phone when mail arrives
MessageIndexer is already a durable consumer of email.inbox.> and already resolves the mailbox OWNER, so it is where this belongs β one notification per genuine...
Preview variables for the two email-change letters
Both new canonical templates render in the console the moment this service is rebuilt onto the shared library that carries them; without a sampleVariables arm t...
Resolve mailbox OAuth credentials per organization
Google, Microsoft 365 and Zoho mailbox connects now read whichever app the org is on β its own registration if it brought one, otherwise Kamo's platform app. T...
A library of footer badges, and the row each scope prints
The signature footer went from one image to a row of them. The library is org-wide; the selection and its order are per scope, so an image uploaded while editin...
Internal endpoint for importing contacts from an external system
VOIPService's JustCall integration needs to land a customer's contacts in the right member's address book, and this is where contacts are written: a Contact map...
Let a sent message carry images inside its body
The body had four possible shapes and send() could only build three. The multipart/related branch β the one that puts an image where the HTML's cid: points at i...
Send the meeting invitation the guest list implied
Adding somebody to an event's guest list did not tell them. Only a reminder ever wrote to a guest, and a reminder reaches a member in-app and an outside address...
Let a transactional send carry one attachment
Added for scheduled reports, which are a rendered PDF or workbook that has to reach people who may have no account β a link would not be a delivery for them. T...
A member is only told about apps they have
A notification kind belongs to an app β a lead callback is Leads, a meeting reminder is Meetings β but raise() asked only whether the member had muted it. So a ...
Preview variables for the bug-status-changed template
The admin preview renders a template against a sample variable map, and this one has no arm β so an org administrator opening SYSTEM_BUG_STATUS_CHANGED would se...
Persist the two company phone toggles
Reads and writes userCompanyPhoneMain / userCompanyPhoneTollFree on the signature field config, so the Content tab's new switches survive a save. saveFieldConf...
Back the personal-mailbox API the console already calls
Eighteen client methods over twelve routes that had been shipping with no backend behind them. A member reaches only their own inbox and configures it only once...
Serve Google's OAuth callback on /api/email/oauth/callback
The platform console needs an address to show an operator setting up the Google Workspace registration, the way VOIP has /api/voip/oauth/callback. The only emai...
Serve the home-page unread digest from the index, never from IMAP
A widget on the home page is on screen for everyone all day, whether or not anybody is reading mail β and a member's IMAP connections are a shared budget of ten...
Serve and announce the home-page calendar widgets
CalendarWidgetService answers what a widget actually needs and the calendar page hasEarlier/hasLater so an arrow greys out instead of offering an empty page. Th...
Scan the org's mailboxes for a lead's past e-mail on request
Serves the "Associate Past E-Mails w/ Lead" tick-box. Given a lead and the address just recorded on it, read INBOX and Sent of every mailbox in that lead's orga...
Publish every change of state, not only arrivals
A member has this open in more than one tab about as often as not, and often on a phone as well. Each of those holds its own socket and draws its own bell, and ...
A paged, filterable history
The centre could answer one question: the most recent hundred, unread or not, with no way to ask for what had already been read. So anything a member had seen w...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.