KamoCRM

Email unsubscribes reach the do-not-contact list, and campaigns, shares and an SI's mail check it

FeatureEmailService
Shipped
October 15, 2026 at 5:06 AM UTC
Author
Kamo
Commit
31ddbfe

Owner 2026-10-15: one company-wide do-not-contact list per organization, scoped per channel; an email unsubscribe blocks email only. - Writer: MirroringEmailOptOutLedger (the EmailOptOutLedger bean) hands every applied change to EmailDoNotContactMirror. A whole unsubscribe (preference page, one-click, List-Unsubscribe mailto, relay) lists the address for EMAIL (source EMAIL_UNSUBSCRIBE, recorded by "Recipient (<source>)" or "Mail system (<source>)"); the person allowing mail again takes it off (EMAIL_RESUBSCRIBE), a member lifting it with the person's consent takes it off with their note (MANUAL). Bounces, complaints, topics and pauses stay the email list's own. Written after the opt-out commits, REQUIRES_NEW, never thrown: an unsubscribe never fails because of the mirror. - Readers: SuppressionService (campaigns, article shares) and SuppressionLookup (eligibility) also count the EMAIL entries, so an address added or imported in Settings -> Phone -> Do not contact is withheld from marketing mail. POST /api/email/send refuses an SI (mail_send, mail_reply, mail_forward) 409 {reason: DO_NOT_CONTACT, error, addresses} on the final recipients, 503 when the list cannot be read; a person's own correspondence keeps the existing rules. Tests: EmailDoNotContactMirrorTest, DoNotContactSuppressionTest, SiMailDoNotContactGuardTest, EmailControllerSendContractTest (RED on origin/main, GREEN here), the other EmailController tests, DeliverabilityWiringTest, SuppressionLookupTest, BulkEmailWiringTest, the arch ratchets.

All changes

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.

Start Free ForeverView Pricing