மாற்று பதிவேடு
என்ன புதிது.
அது கப்பல்கள் போல், தளத்தில் ஒவ்வொரு மேம்பாடு.
· EmailService
DKIM keys and a relaxed/relaxed rsa-sha256 signer for organizations' sending domains
DkimKeys generates a domain's RSA-2048 key (kept in the org's encrypted bulk provider blob) and the TXT value a member publishes at kamoXXXXXX._domainkey.<domain>. DkimSigner signs a message exactly as the transport will send it — read from writeTo after the final saveChanges — covering From and whichever of the headers receivers and feedback loops use are present (Feedback-ID included, never an X- header, since the relay strips X-Kamo-Ref). Verified in tests by Apache jDKIM, an independent implementation: a campaign- shaped message verifies, still verifies once the relay strips X-Kamo-Ref, and fails on a changed body or Subject.
· EmailService
DNS for sending domains — each domain's own nameservers first, SPF evaluated, DNS provider named
LiveDnsResolver asks the zone's authoritative nameservers (found through 1.1.1.1/8.8.8.8), so a record a member just saved at their DNS provider is seen at once rather than after a resolver's negative cache expires; it falls back to the public resolvers on a referral or a timeout, and retries over TCP when an answer does not fit a datagram (dnsjava 3.5 fails to parse google.com's truncated TXT otherwise). SpfEvaluator evaluates a domain's policy for one IPv4 address as a receiving server would (RFC 7208: **************** redirect, the 10-lookup and 2-void-lookup limits), so a CNAME to contact.kamocrm.com, an include, or a bare ip4 all read the same. DnsProviders names the company running a zone's DNS from its nameservers, for provider-specific instructions.
· EmailService
The shared org relay (47.181.8.86) is seeded for every organization; the platform relay stays KamoCRM's
PlatformRelayProperties now knows both in-cluster relays: postfix-bulk (47.181.8.87, hello.kamocrm.com) and postfix-orgs (47.181.8.86), with the egress address, DKIM selector and the DNS target organizations' sending domains CNAME to (contact.kamocrm.com). Both count as relays, so both get delivery reports. The startup backfill seeds k3m1-org-relay as a SHARED platform host (STARTTLS + PLAIN sign-in from the optional Secret bulk-org-relay-auth, self-signed so tlsVerify off), switched off in KamoCRM's own rotation so its mail stays on .87; seeded off until a sign-in is configured, and its credentials follow config on every start. k3m1-bulk-relay is made PLATFORM_ONLY once.
· KlusterServices
Postfix-orgs, the shared bulk relay every organization sends through as 47.181.8.86
KamoCRM's own campaign mail keeps leaving through postfix-bulk (47.181.8.87) and regular mail through postfix (47.181.8.84). Every other organization's campaign mail gets its own IP: postfix-orgs runs on k3m1's host network and binds its outgoing SMTP to eno1 (k3m1-ext), which node-config/k3m1-ext routes out that port and nothing else uses. HELO is contact.kamocrm.com, the name Frontier is setting as the IP's PTR. A hostNetwork listener has no NetworkPolicy, so submission is SASL over required STARTTLS on port 587 of the node's internal address, from cluster addresses only (secrets in mail/SECRETS.md). It relays only as the sending domains EmailService has verified, refreshed every minute — until that map arrives, nothing. The same egress guard as postfix-bulk holds delivery until eno1 is confirmed to leave as .86, and the same delivery-events sidecar reports results. Every refusal is a 4xx, never a 5xx. Appended to the files the deploy workflow already applies, so the workflow (and a full reconcile of every component) is untouched.
· kamo-internal
The campaign tabs get scroll arrows back, without hiding the selected tab
On a screen too narrow for the five sub-tabs, the strip now shows left and right arrows beside it, as the maximized-window tab strip does. It had none since the first version, where MUI's "auto" scroll buttons cut a tab opened by link (?tab=archived) in half: they appear only after MUI has scrolled the selected tab into view, take 80px from the strip, and MUI's own scroll animation then overwrote any correction. CampaignTabStrip puts the tabs in a plain scroller with no bar of its own (data-kamo-scrollbar="off"), shows the arrows only while the tabs overflow, and scrolls the selected tab back into view whenever it changes and whenever the arrows coming or going changes the width. arrowState and ARROW_SCROLL_FRACTION come from maximizedTabStripModel, so both strips agree on when an arrow is live and how far it moves. Checked in Chrome: at 1600px no arrows; at 700px and 520px every tab opened by link is fully visible with the indicator under it, including the middle ones; the arrows page the strip each way; ArrowRight moves focus along the tabs and keeps the focused tab in view, and Enter selects it. Its arrow labels are components.scroll's, repaired by hand in every locale in kamo-translation-dictionary cc484906 (Arabic had "damn it" for Scroll right). Vitest reds here are not from this change and fail identically on main: oneScrollbar.test.ts (on NavTopMenuPanel.tsx) and recipientStatus.test.ts (4, lagging 98156e52).
· kamo-internal
A live send in the Email Campaigns widget gets a spotlight card
With one send in progress, the In-Progress tab was a single thin row in a pane with room for far more, at the moment the widget matters most. When every campaign on a tab is sending or paused, and the panel gives each at least its minimum height, each now gets a card: - how far through the list it is, in a large number rounded down, so it never says 100% with anything left; - its lane at 10px, the head and the sheen visible; - pace and time left, and "N of total"; - delivered, opened, clicked and bounced, as a funnel beside the pace chart on a card with height to spare (true bar lengths and rates), or as a row of figures on a shorter or narrower one; - its pace over the last 5 minutes, a bar per 15 seconds on the clock's own boundaries. The bar still filling is lit, and time before the pane saw the send is a stub, not a zero. The card has the row's link and pause/resume (both now shared, from CampaignRow). It holds through the finish it celebrates. Two sends sit side by side in a wide pane. The choice reads only the panel's size, which nothing inside it can change, so it cannot flicker. A card that overflows anyway is remembered for that panel size, which then shows rows. The layouts inside the card are container queries on the card's own size. Model: paceBuckets spreads what moved between two frames over the time between them. pushSample takes a window, and the hook keeps five minutes per live send, through a pause and a finish. Checked headless on a 10x clock with a varying pace, in regular, wide, tall, 460 and 668x420 panes, and with two sends in the wide and tall panes: cards fit with no overflow, and the pace bars follow the frames, including a quiet stretch. Nothing moves under reduced motion.
· kamo-internal
Email Campaigns widget tabs are the campaigns screen's stages
The widget filed campaigns under its own Live / Sent / Drafts, which put a cancelled campaign under Sent and a scheduled one under Live. Its tabs are now In-Progress, Not Started, Paused and Complete, filed by campaignTabOf and named with marketing.bulkEmail.tabs.*, so the launchpad and /marketing/email never put one campaign in two places. Counts come from EmailService's new `stages` (7481f15); an older server's reply is counted from the campaigns it sent. Choosing the tab: - Until the member picks one, the pane lands on what is happening and stays while that tab still holds anything. It moves only to show a send that just started, or when its own tab runs out. A send that just finished holds In-Progress for its celebration, then the pane moves on. - A row the member pauses or resumes stays where they pressed it, with its Resume, until they leave the tab. - Stage names show where they fit beside "N more", measured against a hidden copy in the member's language; otherwise each stage shows its icon, with the name in its tooltip and accessible name. Fixes found while checking in the browser: - Sparks from new opens and clicks on a finished campaign were never visible: the figures line clipped them. It now clips only below and beside itself, and the sparks fly from the meter's end, so they survive the bars dropping. - A row too narrow for its figures drew the Opened meter over Clicked. The bars go first below 340px, and anything that still does not fit wraps out of sight whole. - A KPI tile too narrow for its name and rate ellipsized the name ("Ope..."). The rate now wraps out of sight instead. - "N more" counted the rows leaving the previous tab. Checked headless at pane widths 322 to 1360: no row cut, no tab overflow, the names/icons switch with and without "N more", arrow keys, a finish held then moved on, and sparks unclipped at 668 and 460. The pause test's server now answers with the paused campaign. It failed under load when the widget's re-read 400ms after a pause returned the campaign still sending.
· EmailService
The campaigns widget reads by stage, the stages the campaigns screen files by
The launchpad widget's read now groups and counts campaigns by the stages /marketing/email's tabs use: In-Progress (sending), Not Started (draft or scheduled), Paused (paused or cancelled), Complete (sent). Failed files by how far its send got, as campaignTabOf does there. Each stage is capped on its own: scheduled before drafts and soonest first; paused before cancelled. `stages` is added beside `groups`, which stays until no widget build that reads it is deployed. Before this, a cancelled campaign counted as finished, which is where the user decided today it does not belong.
· kamo-internal
The campaigns list follows what colleagues do to a campaign, live
BulkEmailWorkspace takes the change frames useEmailCampaignsWebSocket now delivers (onChanged, d0b56adf). A frame names a campaign and what happened to it, never the campaign itself, so the list reads that one campaign again through GET /campaigns/{id} and puts it in place. A colleague's archive or restore moves the row between tabs here as it happens, a new or duplicated campaign appears, and a deleted one, or one that now answers 404, leaves the list. The staged outbox count follows the same frames. createCampaignChangeFollower keeps one read per campaign at a time. Frames that arrive while a campaign is being read (a list uploaded in slices announces every slice) ask for one more read once it returns, so a burst costs two reads and an older answer never lands on top of a newer one. A campaign deleted while its read was on the way is not put back when it returns. Checked in Chrome against the real component by pushing frames through the hook: a restore moved the row out of Archived with one read, a delete removed its row with none, a created draft appeared under Not Started, and five frames for one campaign cost two reads.
· kamo-internal
The Email Campaigns widget, live on the launchpad
A new home widget, email.campaigns, on the launchpad of everybody who can open the campaigns. Its five rights (EMAIL_CAMPAIGNS_WIDGET, 321-325) hang off VIEW_BULK_EMAIL, and ADDED, OPTIONAL and MOVABLE seed from it in SecurityService, so it arrives on and is the member's to remove. The gate also asks for the Marketing app and VIEW_BULK_EMAIL itself. What it shows, reading EmailService's new /api/email/widget/campaigns once and moving live after that: - Four figures for the last two weeks (sent, opens, clicks, bounces), each with a bar a day that reads by pointer or arrow keys, and open and click rates out of delivered mail by the rule the deliverability card uses (engagementRate, now shared by both). - Live / Sent / Drafts, with counts. A sending campaign is a lane that moves, with its pace and time left; opens and clicks rise off it as they land. A paused one says why it cannot send; a scheduled one counts down; a sent one shows its open and click rates. Pause and resume sit on the row for a member with MANAGE_BULK_EMAIL. - A campaign that finishes while the pane watches stays on Live for its moment, with a burst, before it moves to Sent. - The bottom line carries the news as it happens; a wide pane keeps it in a column with today's totals. - It lays out for its room: two figures and compact rows at one column, whole rows only at any height (the list wraps rather than cuts), and two columns of campaigns in a wide pane only once one would hide some. The live half rides /topic/campaigns/<orgId>. useEmailCampaignsWebSocket reads every frame through readCampaignFrame and gains onEngagement, onChanged and onSnapshot, plus requestSnapshot, which the widget turns off so a launchpad never asks the whole organization for a snapshot. onProgress still sees only progress frames. The finish chime now fires on a transition into SENT, not on every later frame that still says SENT (late bounce and delivery reports republish it for hours), and the hook no longer writes its handlers ref during render. Verified with tsc, the guard scripts, the new and affected vitest files, and a headless harness over the real component at 1x1, 2x1, 2x2, 2x3 and full width.
· kamo-internal
In-Progress, Not Started, Paused, Complete and Archived tabs, and the ⋮ opens its menu
/marketing/email splits the campaigns into five sub-tabs, each with a live count, and every campaign is under exactly one: - In-Progress: sending - Not Started: drafts and scheduled campaigns - Paused: paused and cancelled campaigns - Complete: sent - Archived: whatever a member archived Failed is not a stage of its own: recipients fail, a campaign does not, and EmailService never sets it. A campaign that has it is filed by how far its send got. The tab lives in ?tab=, so a reload, the back button and a shared link keep it; a campaign changes tab by itself as its status changes live; and an empty tab says what it is for. Archive and Restore are in each campaign's ⋮ menu (emailservice 39ffa54). Only a campaign with nothing left to send can be archived: a draft, or a sent or cancelled one. The ⋮ button opened the campaign as well as its menu. AG Grid fires rowClicked from its own native listener on the row container, which runs before React's delegated onClick, so the cell renderer's stopPropagation came too late and the row navigated to the editor under the menu. The row handler now lets through clicks inside a data-campaign-own-click control, as the Tracking Codes grid does, and a click on the campaign's name no longer pushes the editor route twice. Checked in Chrome against the real component: on origin/main the ⋮ click went to **************** with this change it opens the menu and goes nowhere. Tabs, counts, archive and restore, empty tabs and a 520px strip were checked the same way. The wording is in kamo-translation-dictionary 02109cfc, pushed first and written by hand in every locale.
· EmailService
Archive a campaign, and the widget leaves archived ones out
POST **************** and /unarchive (MANAGE_BULK_EMAIL) set and clear the campaign's archivedAt, and CampaignDTO carries it. The /marketing/email screen files an archived campaign under its own Archived tab. - Only a campaign with nothing left to send can be archived. A scheduled, sending or paused one is refused: cancel it first. Archiving it again keeps the first date. - Send now, Schedule and Send to latest clear archivedAt, so a campaign sent again is back on the working tabs. - Both are announced as ARCHIVED / UNARCHIVED change frames, so every open campaigns screen and the home widget update live. - CampaignWidgetReads leaves archived campaigns out of recent, inStatuses and countByStatus. Builds on kamo-shared-library ebdd15f1 **************** and the archived_at column, both already in place. TransactionalSendRatchetTest was red on main before this commit and still is; nothing here touches sendToUser. The other 1554 tests pass.
· kamo-shared-library
A campaign can be archived
EmailCampaignEntity gains archivedAt (archived_at): when a member archived the campaign, null while it is not. The /marketing/email screen gets an Archived tab and the home widget leaves archived campaigns out. The column is already on the cluster, applied by hand as every schema change is: ALTER TABLE email_campaign.email_campaigns ADD COLUMN IF NOT EXISTS archived_at timestamp(6) with time zone; Nullable with no default, like the other instants on the table, so code built before this commit reads the table unchanged.
· EmailService
A live read and live frames for the Email Campaigns home widget
GET /api/email/widget/campaigns is the launchpad widget's one read, built for a pane that sits on every campaign viewer's home page all day: lean rows with no body (CampaignLine), live campaigns first (sending, paused, scheduled soonest), then the most recently finished and the most recently edited drafts, each group capped; exact counts per group; every shown campaign's opens, clicks and relay reports in one pipelined Redis read; the organization's last fourteen days; its tracking choice; and the server clock for countdowns. It takes VIEW_BULK_EMAIL, as the campaigns list does. Three kinds of frame now ride /topic/campaigns/<orgId> beside progress: - engagement: CampaignEngagementPulse coalesces first opens, first clicks and relay reports per campaign and sends at most one frame a second per pod, with what is new since the last frame and the totals as Redis holds them. - changed: CampaignController announces a member's create, edit, delete, duplicate, schedule, send, pause, resume, cancel, audience and recipient changes after the service call has committed, as an id and a verb only. - Neither carries a top-level campaignId. Every campaigns screen reads a frame with one as progress and copies its counters onto the row, so a frame of another shape carrying one would blank the counts on screens still running the older code. The sender also counts the day's accepted messages once per batch (stats.daily "sent"), which the widget's two-week sparklines draw from. The full suite is 1534 tests with one failure, TransactionalSendRatchetTest, which counts three sendToUser overloads since c47fcf6 and is unrelated.
· SecurityService
The Email Campaigns widget arrives on for everyone who can open the campaigns
EMAIL_CAMPAIGNS_WIDGET and its ADDED, OPTIONAL and MOVABLE policies seed from VIEW_BULK_EMAIL on the boot that introduces them, so the pane lands on the launchpad of every member who can already read the campaigns, and it is theirs to remove or move. REQUIRED does not seed, as for every widget: a deploy never pins a pane to anybody's home page.
· kamo-shared-library
The Email Campaigns home widget's five rights
EMAIL_CAMPAIGNS_WIDGET (321) and its four policy children (322-325), hung off VIEW_BULK_EMAIL: every read behind the widget, the campaign list, its counters and the live progress frames, already takes that right, so the widget can never be a way round it. 326 is the next free id.
· kamo-internal
Email Campaigns is the first Marketing tab
The Marketing Tools tab strip now reads Email Campaigns, Marketing Materials, Social Media, Tracking Codes. Bulk Email moves to the front under its new name, and Materials takes the full name its own page already shows. The /marketing index lands on the first tab a member can open, so its order moves with the strip: Email Campaigns by default, Marketing Materials for a member without the email right. The route, the label keys and VIEW_BULK_EMAIL keep their names. The wording is in kamo-translation-dictionary 98a21a21, pushed first, with the two labels written by hand in every locale.
· kamo-marketing
Solutions moves under Resources, one level down
The top row carried five categories before the phone number, the language selector and three CTAs. Solutions is now the first item inside Resources, above the two article links, and keeps its eight industry pages as a third-level flyout. Nothing in the renderer changed: NavChild already carries its own `children`, and Legal has been nested under Resources this way all along, on desktop and in the mobile accordion both. This is only the data. "All Industries" stays as the first grandchild rather than being folded into the parent row, because the mobile parent is a toggle button, not a link — /solutions itself is unreachable on a phone without it. /solutions pages still light their category up: isCategoryActive already walks the grandchildren, so Resources highlights where Solutions used to. Confirmed on /en/solutions/real-estate — Resources renders #ffffff on rgba(107,142,58,0.1) and the other four stay #9a9a9a. Verified against the running page, not the source. The top row reads Features, Services, Pricing, Company, Resources; hovering Resources then Solutions opens the eight industries with their /en prefixes. The flyout is 200px wide and its right edge lands at 1001, 1001, 1076 and 1316 for viewports of 1024 (the lg breakpoint where this nav appears at all), 1280, 1440 and 1920 — inside the viewport at every one, with no horizontal overflow. On a 390-wide phone Resources opens, Solutions opens under it, and all eight links are there. tsc, check-client-navigation and check-i18n-keys are clean.
· kamo-marketing
Application development, web design and web marketing
**************** argues that a business paying an agency per seat is paying for software the agency did not build, host, or keep running. It ends by asking who should get that money and has never had a door to point at. This is the door. 59 pages under /services: a hub, three pillars, 30 capability pages, 12 evidence pages, 5 comparisons, and six on how an engagement actually runs. Structure in TypeScript, prose in the dictionary — the pattern the 37 industries, 45 integrations and 41 glossary terms already use, so a translator can rewrite every sentence and cannot damage a slug or a route. Four new collections, gated on the `services` namespace rather than `collections`: the latter is already translated into many locales and reusing it would claim a French service page exists before a word of it does. Today every service page canonicals to English and appears in the English sitemap only, which is what the machinery already does for a page whose copy has not been translated. The portfolio is our own platform, because it is the only one we can let a visitor inspect: the mail servers, the telephone switches, the WebRTC media stack, the attribution pipeline, the cluster, and this website. Every number on those pages carries the command it came from and the day it was read, and scripts/check-services.ts fails the build on a fact that has neither — along with a pillar slug that a static /services child would shadow, a dangling evidence reference, an orphan work page, and a comparison that cannot name a case against itself. That last rule is not decoration: a comparison page that cannot argue against itself is an advertisement wearing a table. Pricing publishes the mechanism rather than a table. We quote after discovery and fix the number first; what moves it, what is never in it (no percentage of ad spend, no markup on anything bought for you) and when you find out are all on /services/how-we-quote. /services/start posts an existing topic deliberately — the support service resolves topicTitle against topics that already exist and answers 400 otherwise, so a "Web Design" topic would have compiled, deployed, looked correct, and rejected every submission. Two accessibility fixes that are not strictly this feature but ship on every page of it. globals.css:52 says never to use a raw --color-primary* for small text; the cookie consent banner did, at 2.18:1, on every page of the site, and RelatedContent did the same on solutions, integrations, alternatives and glossary. Both now use the readable accent, and the banner's policy links are white — --color-accent-on-canvas is computed to hit exactly 4.5:1 against the canvas, which leaves no headroom for the slightly raised surface the banner sits on. All 19 service pages, the homepage, /pricing and /solutions/* now pass axe at WCAG 2.2 AA with zero violations; the fees article's own table-of-contents badges still fail and are left alone.
· kamo-marketing
An article on counting your software subscriptions, and the banner points to it
/all-in-one-business-software asks how many subscriptions a business is paying for: a checklist that counts them and totals the month and the year as the reader ticks it, what a stack costs beyond its invoices, what all-in-one has to mean to be worth anything, the questions to ask before consolidating, and how KamoCRM covers the same ground. Two outside figures, each named in the copy and linked at the foot: Zylo's 2026 SaaS Management Index on unused licences and who controls software spend, and Cledara's 2025 Software Spend Report on how far businesses underestimate their tool count. The banner's public service announcement now points here rather than at the seat-fee article. Its label is a new key, not a new value for the old one: the dictionary pipeline skips a key whose translation already differs from the English, so every other language would have kept the old question over a link to this article. Listed in the Resources menu, the Resources hub and the sitemap.
· kamo-internal
The launchpad bands open into the bar's own dropdown surface
The administrator bar and the pinned-app bands are painted with the top bar's gradient, and then answered a hover with something from another app entirely: a white tooltip card floating twelve pixels below the band, and — on a pinned app's chip — an MUI popover with grey captions and no seam to the thing it came out of. Nine of them, each appearing and vanishing on its own. Wrong palette, wrong shape, arriving from the wrong place. They now open into the SAME surface the top bar's menus use. It is painted in the organization's brand, its top edge takes the exact colour the band has at the spot it leaves from, the fillets weld it to that edge, and one surface glides and resizes from entry to entry as the pointer travels the row — so a band reads as opening rather than as a card laid over the launchpad. • NavTopMenuHost takes any bar-like element, not just NavTop's header: an `edgeGap` per host, because a band has rounded corners the bar does not, and both the panel and its fillet have to meet painted band. The room a panel has to grow into is measured from where it actually starts (--kmenu-max-height) rather than from the nav's height, which means nothing halfway down a page. • NavTopMenuPanel gains `kind="note"`: the same surface and the same motion, carrying prose about the trigger instead of choices. It claims no menu role and takes no focus, so the administrator bar's descriptions stay descriptions and Enter still follows the link they describe. • A pinned app's chip draws the very rows the nav draws (NavToolSectionsPanel, lifted out of NavToolBarMenu), so one option answers with one menu wherever it is offered — nested groups, badges, filter box and all. • Both bands read their gradient from navTopSeam.ts. A literal copy of the stops is precisely what drifts, and the seam is only invisible while the panel and the band agree on it. Tests: the administrator bar rendered and worked with a mouse, a finger and the keyboard (one surface, handed from entry to entry, never stealing focus), plus a source guard that neither band reaches for a popover or a second copy of the gradient again.
· kamo-marketing
Embed homepage marketing video on white-label CRM fees article
Give readers the same product reel from the homepage after the intro, before the resold-software section.
· kamo-shared-library
WebinarNotifyMember, an organization's Additionally Notified list
One row per (organization, team member) in webinar_notify_member, created by KamoInitializerService's WebinarNotifyMemberMigration and already applied. The repository fetch-joins the team member, since every reader wants the person. No foreign key, matching the migration, so the initializer's ddl-auto update never adds one.
· MediaService
Blind-copy the Additionally Notified team members on presenter emails
GET/PUT /api/webinar/notify-members (MANAGE_SUPPORT) read and replace an organization's Additionally Notified list; every id must be a team member of that organization, at most 25. WebinarEmailNotifier adds the owning organization's list as blind copies on every letter to a presenter — booked, attendee joined, attendee cancelled, session cancelled, reminder — never on an attendee's letter, never the presenter's own address, each person once, and only people still eligible to be written to.
· EmailService
Blind copies on the service-to-service template send
SendTransactionalRequest takes an optional bcc list, carried through sendToUser and deliver onto the SMTP envelope — including the platform-address retry — so a copied person gets exactly the message the addressee gets. blindCopies trims, drops the addressee and duplicates, and caps the list at 25. No existing caller changes: without bcc the send takes the path it always did.
· InitializerService
Webinar_notify_member, the team members copied on presenter emails
One row per (organization, team member): the Additionally Notified list on the Scheduled Webinars settings tab. MediaService blind-copies these addresses on every webinar email sent to a presenter for the organization that owns the webinar type. A new table, so no table other services read is altered; no rows means nobody is copied. Applied to production 2026-09-13.
· kamo-internal
Let an admin choose who is copied on presenter emails
The Scheduled Webinars tab gains an "Additionally Notified" section beside the list of webinar types. The team members chosen there get a blind copy of every automated email sent to a presenter (new booking, attendee joined or cancelled, session cancelled, reminder) for all of the organization's webinars. It reads and replaces the list through /api/webinar/notify-members, picks people with the same multi-select directory picker the Presenters tab uses, and saves explicitly with an unsaved-changes hint and a saved confirmation. A 403 hides the section, a failed load offers a retry rather than an empty list, a refused save shows the server's reason, and another admin's change arrives live over the webinar org-change topic.
· kamo-shared-library
Canonical templates for every webinar booking letter
Seven meet.webinar_* templates — the presenter's booked / attendee joined / attendee cancelled notices, the attendee's confirmation and cancellation, the session-cancelled letter and the scheduled reminder — registered in CANONICAL_KEYS, with WebinarEmail building the one variable map that fills them: times in the reader's zone, the booker's form answers for the presenter, the guest join link and personal cancel link for attendees, the console link for the presenter, and blocks that collapse when there is nothing to show.
· kamo-marketing
Let a visitor cancel their registration from the emailed link
/webinar/cancel/<token> is where a webinar confirmation email sends someone who can no longer make it. It looks the registration up through the public webinar API, shows the webinar, presenter, date and time (in the visitor's own locale and timezone, with the zone named) and length, and cancels it behind a confirm step. An unknown link, an already cancelled registration and a webinar that has already been held each get their own state, and a refusal from the API is shown as the API words it. noindex/nofollow, and not in the sitemap: the path is dynamic, so the sitemap walker skips it and nothing generates entries for it. Copy lives in the new webinarCancel namespace.
· KlusterServices
Sign Feedback-ID in DKIM signatures
Gmail counts a campaign message toward its Feedback Loop only when the Feedback-ID header is covered by the sending domain's DKIM signature. local.d/dkim.conf keeps rspamd's default signed headers and adds feedback-id.

உங்கள் உலகம் காத்திருக்கிறது.
நிமிடங்களில் இலவசமாகத் தொடங்கவும். நீங்கள் தயாராக இருக்கும் போது பொருட்களை சேர்க்கவும் - எந்த மாற்றமும் இல்லை, புதிய உள்நுழைவுகள் இல்லை.
எப்பொழுதும் இலவசம் — கடன் அட்டை தேவையில்லை