Startup checks for the one-address-per-campaign index instead of creating it
EmailService ran CREATE UNIQUE INDEX on every startup. DDL on this cluster costs every service about fifteen minutes of "schema version mismatch", too much to r...
Read the platform's hex secret as hex, and stretch a short key instead of refusing it
JWTUtils decoded security-service.*** as Base64 only. The platform's secret is 64 hex digits (32 bytes), which Base64 turns into 48 meaningless bytes; those wer...
The campaign editor's Recipients and Send steps fill the width instead of an 860px column
The campaign editor writes edge to edge, merge tokens in their own column
The subject sits under the campaign title and the audience beside the status chip, so the compose step is the email body alone, filling the middle of the page, ...
Review fixes to the provider blob, delivery reports and test send
The provider blob is always written in a transaction of its own, so its lock query stays the first statement and a lost lock race can be retried even when a cal...
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 ...
Review fixes for the bulk sender, warm-up and pacing banner
A pass over everything the bulk sender and warm-up screens are built from, fixing what it found: - a warm-up poll that started while a write was on the wire co...
A from the platform relay would refuse pauses the campaign instead of failing its recipients
Failing the recipients made every one of them contacted, so once the From was fixed the campaign could never send to those addresses. The send path now checks t...
A campaign the backend cannot send says why and how to fix it
EmailService now pauses a campaign instead of failing its recipients one by one when it cannot send it: its From is off the platform relay's domain, or there is...
Email settings redirect lists its hook dependencies
The rights check that sends a member without MANAGE_EMAIL_SETTINGS back to /settings ran in an effect that listed only `loading`, so the linter flagged the miss...
Warm-up says idle days don't count and draws the dates that slid
A warm-up day in which nothing is sent no longer counts: the run slides forward and the day repeats when sending resumes, which makes switching warm-up on befor...
The platform relay is recognised by one host-and-port check everywhere
Refusing an off-domain From matched the relay by host and port, while the warm-up's delivery tracking and the test send's advice matched by host alone, so a pro...
Live campaign progress reaches browsers on every replica
The STOMP broker is in-heap per pod and progress is raised on whichever pod holds the dispatch lease or received a delivery report, so browsers on the other pod...
The bulk sender tab fills the width it is given
The tab sat in a 1120px column centred in the settings card, so on a wide screen most of the card was empty margin around a narrow form. The column is gone, alo...
This relay only sends as kamocrm.com
Anything in mynetworks (every pod, and the LAN) could relay through 47.181.8.84 as any domain, which receivers treat as spoofing and charge to this IP; that is ...
The delivery-events sidecar drops a batch EmailService rejects as invalid
A 400/413/422 means the batch itself will never be accepted; retrying it forever would hold back every result queued behind it. It is logged and dropped. 401/40...
Deploy postfix-bulk with the rest of the mail server [skip ci]
The bulk relay's four manifests were applied by hand because the mail step applies its files by name. It now applies them too, refuses to continue if the sideca...
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...
Spring can build the bulk-email, warm-up and delivery beans
Several of them have a second constructor taking a clock for tests, and with two constructors Spring needs to be told which to use or the whole context fails at...
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...
Recipient rows carry contacted, and already-sent rows keep their mark beside the status
EmailService's recipient DTO now reports whether the campaign has already emailed each address. The mapper carries it through, together with lastError, and fill...
The campaign From field says when the bulk relay would refuse it
The campaign's own From field, defaulted from the bulk provider, warns when the address is off the provider's domain, as mail that may land in spam. On the plat...
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 smtp envelope sender is set to the bare from address
Campaign mail now carries a display name in its From, and the relay it goes through accepts only its own sender domain, judged by MAIL FROM. mail.smtp.from is n...
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 relay that never signs in counts as a configured bulk sender
/marketing/email let an organization send campaigns only once its bulk provider had a stored password. The platform relay listens on port 25 with no TLS and no ...
Recipient grid fills name, source and error from either row shape
The campaign recipient grid read name, sourceType and errorMessage from rows that only carried email, status, sentAt and lastError, so three of its five columns...
Warm-up pacing banner on /marketing/email
While the new host warm-up is pacing, campaigns go out far more slowly than their own settings say, and nothing on the campaign said why. The campaigns workspac...
Typed bulk sender client with field errors and the warm-up endpoints
The client knew a provider status called PENDING that the backend never sends, so a sender that was saved but not yet tested fell through to "Not configured". I...
Connection rule, presets and warm-up view model behind the bulk sender
The pure pieces the new screens are built on, each with tests: - the backend's port-first connection rule, mirrored so the form can say how KamoCRM will conn...
Rebuilt bulk sender tab with a new host warm-up panel
The Bulk Email Sender tab is rebuilt from one long form into a set of cards over a draft of the saved state: - a masthead with the live status (a saved but unt...
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...
A missing mailing address no longer blocks sending
The preflight treated an org with no address on file as an error and kept Send and Schedule disabled. The address still goes in the footer when there is one; wh...
A missing mailing address no longer blocks sending
assertSendable refused to schedule or send a campaign without a physical address. The org's address still goes in the footer when it has one on file; when it ha...
The smtp connection rule is stated once and shared
SmtpConnectionRule holds the port convention createSmtpSession applies (465 implicit TLS, 587 STARTTLS, STARTTLS elsewhere only when asked for, anything else pl...
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 hold'em invitation is gated on the right to play hold'em
HOLDEM_INVITE was in neither the gated nor the ungated map, so it shipped ungated and failed the test that exists to catch exactly that.
A line break in a sender's display name can no longer start a header
Jakarta Mail encodes commas, quotes and non-ASCII in a display name but passes a raw CR/LF through, so displayedAs now flattens control characters to spaces bef...
Preview the email as sent, a Custom / No Template audience, and a real From field
Preview: a dialog that renders the editor's own buildBodyHtml() output - the string Save stores and the dispatcher sends - so the audience's header, signature a...
Sign hello.kamocrm.com with its own customer key, by the From domain itself
Campaign mail leaves as hello.kamocrm.com through postfix-bulk. It gets its own DKIM key (selector customer, published as customer._domainkey.hello), and use_es...
Postfix-bulk, a second outbound relay for campaign mail, with delivery results fed back
Campaign mail now leaves through its own relay on k3m1 (egress 47.181.8.87) as hello.kamocrm.com, so complaints about bulk mail never touch the reputation of ma...
Keep hello.kamocrm.com off this relay, and stop three errors on every start
- A @hello.kamocrm.com sender is refused relay here with a 4xx, so bulk mail can only leave through postfix-bulk; delivery to our own domains is unaffected....
The NoReply recorder names the constructor Spring should use
It has two, the second for tests, and with neither marked Spring picks neither: the context failed with "No default constructor found" and took EmailSmtpService...
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...
NoReply Outbound, a god-only audit of mail sent as the platform NoReply address
A third section under Mail, shown only to god-eligible members, opens /messages/outbound/noreply: every message EmailService handed to the relay as the platform...
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 ...
The campaigns grid read counts the API never sent
EmailService's CampaignDTO carries a campaign's counters flat (totalCount, sentCount, failedCount, suppressedCount, bouncedCount, queuedCount), but every screen...
A tracking link can land on the white-label CRM fees article
Adds **************** to the destinations the tracking-link picker offers on /marketing/tracking, with its own icon. The path is locale-free like its siblings: ...
The launch bar ends at the form column, clear of a docked chat window
On /network/create the readiness meter and the Launch organization button sat in a bar across the whole page, under the brand preview too - exactly where docked...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.