MailerLite is an email marketing platform with a straightforward pricing model, GDPR-compliant infrastructure, and a REST API v2. For B2B teams, the integration with Kommo solves a specific problem: automatically add a lead to the right MailerLite group when it moves between pipeline stages, and sync the subscription status back to Kommo. No Zapier, no duplicates - through a custom integration.
MailerLite API v2 uses a Bearer token (Authorization: Bearer {API_KEY}). The main operations are: POST /api/subscribers - create/update a subscriber, POST /api/groups/{id}/subscribers - add to a group, DELETE /api/groups/{id}/subscribers/{email} - remove from a group. Webhooks are triggered by subscriber actions: opened an email, clicked, unsubscribed.
A MailerLite Group is a subscriber list, analogous to a HubSpot List or Mailchimp Audience. Each stage in the Kommo pipeline corresponds to a specific MailerLite group.
Architecture: The Pipeline as a Segmentation Source
Kommo stage "Qualified"
-> webhook leads.status.changed
-> Your server
Your server
-> GET Kommo: contact email and name from the deal
-> MailerLite API: upsert subscriber + kommo_lead_id field
-> MailerLite API: add to group "Qualified Leads"
-> remove from previous group (if any)
MailerLite: send nurturing sequence
-> webhook: subscriber.unsubscribed
-> Your server -> Kommo: note "Unsubscribed from emails"
Implementation: Kommo -> MailerLite
import requests, os
from flask import Flask, request, jsonify
app = Flask(__name__)
ML_API_KEY = os.environ["MAILERLITE_API_KEY"]
ML_BASE = "https://connect.mailerlite.com/api"
ML_HDR = {"Authorization": f"Bearer {ML_API_KEY}", "Content-Type": "application/json"}
KOMMO_SUBDOMAIN = os.environ["KOMMO_SUBDOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
KOMMO_BASE = f"https://{KOMMO_SUBDOMAIN}.kommo.com/api/v4"
KOMMO_HDR = {"Authorization": f"Bearer {KOMMO_TOKEN}", "Content-Type": "application/json"}
# Mapping of Kommo stages -> MailerLite groups
STAGE_TO_GROUP = {
int(os.environ.get("STAGE_QUALIFIED", "0")): os.environ.get("ML_GROUP_QUALIFIED", ""),
int(os.environ.get("STAGE_PROPOSAL", "0")): os.environ.get("ML_GROUP_PROPOSAL", ""),
int(os.environ.get("STAGE_NEGOTIATION", "0")): os.environ.get("ML_GROUP_NEGOTIATION", ""),
int(os.environ.get("STAGE_WON", "0")): os.environ.get("ML_GROUP_WON", ""),
int(os.environ.get("STAGE_LOST", "0")): os.environ.get("ML_GROUP_LOST", ""),
}
def get_contact_for_lead(lead_id: int) -> tuple[str, str, str]:
r = requests.get(
f"{KOMMO_BASE}/leads/{lead_id}",
headers=KOMMO_HDR,
params={"with": "contacts"},
)
contacts = r.json().get("_embedded", {}).get("contacts", [])
if not contacts:
return "", "", ""
rc = requests.get(
f"{KOMMO_BASE}/contacts/{contacts[0]['id']}",
headers=KOMMO_HDR,
params={"with": "custom_fields_values"},
)
c = rc.json()
email = ""
for cf in c.get("custom_fields_values", []) or []:
if cf.get("field_code") == "EMAIL":
vals = cf.get("values", [])
if vals:
email = vals[0].get("value", "")
break
name = c.get("name", "")
parts = name.split(maxsplit=1)
first = parts[0] if parts else ""
last = parts[1] if len(parts) > 1 else ""
return email, first, last
def upsert_subscriber(email: str, first: str, last: str, lead_id: int) -> dict:
r = requests.post(
f"{ML_BASE}/subscribers",
headers=ML_HDR,
json={
"email": email,
"fields": {
"name": first,
"last_name": last,
"kommo_lead_id": str(lead_id),
},
"status": "active",
},
)
r.raise_for_status()
return r.json().get("data", {})
def add_to_group(subscriber_id: str, group_id: str):
requests.post(
f"{ML_BASE}/subscribers/{subscriber_id}/groups",
headers=ML_HDR,
json={"groups": [group_id]},
)
def remove_from_all_groups(subscriber_id: str, exclude_group: str):
r = requests.get(f"{ML_BASE}/subscribers/{subscriber_id}/groups", headers=ML_HDR)
for g in r.json().get("data", []) or []:
gid = g.get("id", "")
if gid and gid != exclude_group:
requests.delete(
f"{ML_BASE}/subscribers/{subscriber_id}/groups/{gid}",
headers=ML_HDR,
)
def add_note(lead_id: int, text: str):
requests.post(
f"{KOMMO_BASE}/notes",
headers=KOMMO_HDR,
json=[{
"entity_id": lead_id,
"entity_type": "leads",
"note_type": "common",
"params": {"text": text},
}],
)
@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
data = request.json or {}
for lead_data in data.get("leads", {}).get("status", []):
lead_id = lead_data.get("id")
new_status = lead_data.get("status_id")
group_id = STAGE_TO_GROUP.get(new_status)
if not group_id:
continue
email, first, last = get_contact_for_lead(lead_id)
if not email:
continue
sub = upsert_subscriber(email, first, last, lead_id)
sub_id = sub.get("id", "")
if not sub_id:
continue
remove_from_all_groups(sub_id, group_id)
add_to_group(sub_id, group_id)
add_note(lead_id, f"MailerLite: subscriber {email} added to group {group_id}.")
return jsonify({"status": "ok"}), 200
Implementation: MailerLite Webhook -> Kommo
@app.route("/webhooks/mailerlite", methods=["POST"])
def mailerlite_webhook():
# MailerLite signs webhooks via X-Mailerlite-Signature
event = request.json or {}
ev = event.get("event", {})
ev_type = ev.get("type", "")
if ev_type not in ("subscriber.unsubscribed", "subscriber.bounced", "subscriber.junk"):
return jsonify({"status": "ignored"}), 200
subscriber = event.get("subscriber", {}) or {}
fields = subscriber.get("fields", {}) or {}
lead_id = (fields.get("kommo_lead_id") or {}).get("value", "")
email = subscriber.get("email", "")
if not lead_id:
return jsonify({"status": "no_lead_id"}), 200
msgs = {
"subscriber.unsubscribed": f"MailerLite: {email} unsubscribed from the mailing list.",
"subscriber.bounced": f"MailerLite: email {email} is unreachable (bounce).",
"subscriber.junk": f"MailerLite: {email} marked the email as spam.",
}
add_note(int(lead_id), msgs.get(ev_type, f"MailerLite: event {ev_type} for {email}."))
return jsonify({"status": "ok"}), 200
Configuring the MailerLite Webhook
Dashboard -> Settings -> Webhooks -> Add webhook. URL: https://your-server.com/webhooks/mailerlite. Events: select all subscriber events.
MailerLite signs the request body via X-Mailerlite-Signature. The algorithm is HMAC-SHA256 of the body using the webhook secret. Add verification analogous to the example above.
The kommo_lead_id Field in MailerLite
When creating a subscriber via the API, the kommo_lead_id field is passed in fields. MailerLite stores custom fields for each subscriber. For setup: create a custom field kommo_lead_id (type Text) in MailerLite Dashboard -> Settings -> Subscriber fields.
Preventing Duplicates
MailerLite uses email as a unique key - POST /api/subscribers with an existing email will update the record, not create a duplicate. This works correctly only if contacts in Kommo do not have multiple email addresses. Use the primary (first) email from field_code: EMAIL.
Who This Is For
B2B teams with a CRM pipeline in Kommo and email marketing in MailerLite. Especially useful if:
- Different deal stages should receive different nurturing sequences
- You need to stop sending emails when a lead loses interest (lead = Lost -> unsubscribe)
- The team does not want to monitor two tools separately
Similar integrations are described for Kommo + Ortto and Kommo + ActiveCampaign.
Frequently Asked Questions
How do I make sure a subscriber doesn’t receive two emails from different groups?
MailerLite only triggers automation for the group that activates it. If a subscriber is in two groups, they receive both automations. The solution: use remove_from_all_groups before adding to the new group - this guarantees the subscriber is always in only one Kommo-linked group.
Does MailerLite API v1 work with this code?
No. MailerLite v1 is deprecated and unsupported. Use v2 with the base URL https://connect.mailerlite.com/api. The API Key for v2 is created via Dashboard -> Settings -> Developer API.
How do I segment by country through MailerLite from Kommo?
Add the country field to fields when creating the subscriber. Pull the value from the contact’s custom field in Kommo. Then in MailerLite, create a Segment with the condition fields.country = "US" - use it for geo-targeting campaigns.
Summary
Kommo + MailerLite - email marketing from your pipeline:
- Bearer token,
POST /api/subscribers- upsert by email (no duplicates) kommo_lead_idin a custom field for reverse correlationremove_from_all_groups->add_to_group- subscriber is always in the current group- MailerLite webhook
subscriber.unsubscribed-> note in Kommo - One contact = one group = one nurturing sequence
If you need an integration between Kommo and MailerLite or another email platform - describe your task to the Exceltic.dev team.