
Selzy exposes one flat, unversioned REST API at https://api.selzy.com/en/api/{method}, authenticated by an api_key parameter and nothing else, no OAuth, no bearer token, no version segment. There is no official SDK in any language, and as of August 2026 no official Selzy MCP server: the only turnkey agent path is Zapier MCP, which reaches four operations. The compensating fact is that the API is simple enough, form-encoded in, JSON out, one credential, that a self-hosted MCP wrapper is small work.
/v1/ segment, no version header. The language code (en, ua, ru) sits where a version would.MCP is how an agent gets typed, permissioned access to a SaaS product instead of a raw HTTP client and a key. For Selzy, no such server exists from the vendor and the community has not filled the gap, so the question becomes how cheaply you can build the bridge yourself.
Verified August 19, 2026 across the Selzy help center, selzy.com feature pages, the selzy-dev GitHub account and the main MCP directories. The same holds for UniOne, Selzy’s sibling delivery product. Practical routes today: Zapier MCP for 4 operations, or a self-hosted wrapper over api.selzy.com.
Three entries matter when wiring an agent to Selzy. Only one is an actual MCP server, and it is not Selzy’s.
The only turnkey path. Zapier’s MCP endpoint exposes apps connected in your Zapier account, and Selzy is listed, but the connector is narrow: 2 triggers (New Subscriber, New Unsubscriber) and 2 actions (Add a New Contact, Unsubscribe a Contact). Campaigns and stats are unreachable.
Published under Selzy’s own selzy-dev account as an OpenClaw Selzy API Skill. It packages the REST API for an agent but exposes no MCP transport, so it is not an MCP server, useful as prior art for your own wrapper.
The realistic answer for full coverage. One credential, form-encoded parameters, JSON responses, no OAuth dance, no cursors, the surface maps onto MCP tools mechanically. Skeleton below.
An MCP gap is not a dealbreaker, but it changes who does the work: an agent that can only add and unsubscribe contacts, or one that can do everything once your team ships and maintains a wrapper. The upside is that one api_key parameter, one host and a flat method list make Selzy far cheaper to bridge than a platform with OAuth scopes and cursor pagination. If a vendor-maintained MCP server is a hard 2026 requirement, compare Brevo and MailerLite first.
Everything runs through one host and one URL shape: /{lang}/api/{method}, where lang is en, ua or ru and sets the language of error messages, not campaign content. Methods are verbs, not resources, you call sendEmail, never POST /campaigns.
| Base URL | https://api.selzy.com/en/api/{method} |
| Version | Unversioned SHIPS IN PLACE |
| Transport | HTTPS GET or POST; use POST so keys stay out of intermediate logs |
| Response format | JSON-pass format=json explicitly |
| Error shape | HTTP 200 with error and code keys in the body, never trust the status code |
| Pagination | Method-dependent; exportContacts does not paginate at all |
| Bulk operations | importContacts (upsert), exportContacts (async, returns task_uuid) |
| Request timeout | Not documented |
Two consequences follow. There is no deprecation window to watch: method-level changes ship in place, so your own contract tests are the only early-warning system. And because the language code occupies the version slot, tutorials hardcoding /ru/api/ still work, they just return Russian error strings, maddening to debug in a log aggregator. Pin en.
Events cover email_status (sent, delivered, rejected, read, link clicked), subscribe, subscribe_primary, unsubscribe, campaign_status, email_check, user_payment and user_info. The body carries auth (an MD5 hash), num (a sequence number) and events_by_user, an array of login, event_name, event_time and event_data objects. The retry policy is the part to design around, see the gotchas.
Selzy has one authentication mechanism plus one mandatory extra parameter that catches integration builders. No OAuth 2.0 flow, no bearer header, no scoped tokens, a Selzy API key is an account-wide credential.
Every request carries api_key as a query-string or form parameter. Because it travels as a parameter rather than a header, GET writes it into every proxy, CDN and access log between you and Selzy. Use POST for anything carrying a key or a recipient address. There is no per-key scope, so treat the key as root on the account.
API access is off by default. Open Settings › Integration and API › API Access, turn it on, then click Show Full beside the key, which prompts for your registration password.
If you are building an integration Selzy will list, its guidelines require a platform parameter on each API call, a Latin-letters-only identifier for your system, agreed in advance with a Selzy manager. Example: ...&title=***&platform=MySite.
It appears only in the separate Guidelines to create integrations, not on the reference page for sendEmail or subscribe. Authors routinely ship a working connector and learn of the requirement at review time. Thread platform through your HTTP client from day one rather than retrofitting every call site.
Selzy layers a global account ceiling under per-method caps, then adds a daily ceiling for new accounts. All three bite in the same integration.
| Limit | Value | Scope |
|---|---|---|
| Global request cap | 1,200 requests / 60 seconds | Per API key or per IP address |
sendEmail | 60 calls / minute | Per account |
subscribe | 300 requests / 60 seconds | Per account |
checkEmail | 300 requests / 60 seconds | Per account |
createCampaign | 100 requests / 60 seconds | Per account |
| New-account daily ceiling | 1,000 messages / day | sendEmail; rises as delivery statistics prove out |
| Same-recipient interval | 60 seconds minimum | Between two sends to one address |
| Maximum message size | 1 MB | sendEmail |
sendSms fan-out | 150 numbers / call | Per call |
| SMTP throughput (UniOne) | 5,000 emails / hour per connection | Max 10 simultaneous connections |
The workarounds are unglamorous. For contact sync, stop looping subscribe–importContacts does bulk create-and-update in one call and leaves the 300/minute band entirely. For transactional volume, sendEmail at 60 calls/minute under a 1,000/day ceiling is not a transactional pipe; that is what the UniOne relay is for, quoted at up to 150,000 emails/hour, see SMTP settings. And because the cap keys on IP as well as key, workers behind one NAT gateway share one 1,200/minute budget. Rate-limit centrally.
This is the shortest section on the page because there is almost nothing in it. Selzy publishes no client library for api.selzy.com in any language. The one installable piece of Selzy-authored code is a WordPress subscription plugin, a product integration, not an SDK.
| Language | Package | Install | Repo |
|---|---|---|---|
| PHP (WordPress plugin) | selzy-wordpress-subscription-plugin | git clone the repo, or upload the ZIP via Plugins › Add New | selzy-dev on GitHub |
| Ruby (UniOne, not Selzy core) | UniOne Ruby gem | Not published | docs.unione.io |
The WordPress plugin is a small repository, roughly 12 commits on main, no tagged releases, no semantic version and no wordpress.org listing, so no automatic updates. The Ruby gem is listed on UniOne’s integrations page with no gem name, RubyGems link or repository URL, and targets the UniOne Email API rather than api.selzy.com. Neither substitutes for a thin client of your own, roughly an afternoon’s work.
There are none worth naming, and that absence is itself a trap. Package registries return libraries for UniSender, a separate company whose API exposes identical method names. Installing one appears to work at the call-signature level while its default base URL sends your key to the wrong vendor. The only check that matters: which host does it default to?
The surface is a flat method list, not a REST resource tree, so read it by task. Full parameter tables live in the official Selzy API reference.
| Resource | Methods | Description |
|---|---|---|
| Send single email /en/api/sendEmail | GET, POST | Needs email, sender_name, sender_email (confirmed), subject, body, list_id-the list drives the unsubscribe link. |
| Check delivery status /en/api/checkEmail | GET, POST | Status of a message sent with sendEmail. 300 requests / 60 seconds. |
| Create email message /en/api/createEmailMessage | GET, POST | Builds a bulk creative without sending, returning a message_id; updateEmailMessage edits it. |
| Create campaign /en/api/createCampaign | GET, POST | Schedules or launches a bulk send. 100 requests / 60 seconds; cancelCampaign halts one not yet started. |
| Subscribe contact /en/api/subscribe | GET, POST | Adds or updates a recipient with fields and tags, the workhorse. 300 requests / 60 seconds. |
| Unsubscribe contact /en/api/unsubscribe | GET, POST | Sets a global unsubscribed status; exclude only drops list membership. |
| Import contacts (bulk) /en/api/importContacts | POST | Bulk create-and-update in one call, the right endpoint for CRM sync, not a subscribe loop. |
| Export contacts (async) /en/api/exportContacts | GET, POST | Filters on list, email, tag and status; returns a task_uuid polled with getTaskResult. No pagination. |
| Contact lists /en/api/getLists | GET, POST | All lists with ids and codes; createList, updateList, deleteList manage them. |
| Get contact /en/api/getContact | GET, POST | Fields, memberships and tags for one contact; isContactInLists is the cheap membership check. |
| Custom fields and tags /en/api/getFields | GET, POST | User-defined fields, with createField, deleteField and getTags. |
| Email templates /en/api/listTemplates | GET, POST | Templates without bodies for cheap enumeration; getTemplate returns the body. |
| Campaign statistics /en/api/getCampaignCommonStats | GET, POST | Headline figures; getCampaignDeliveryStats per-recipient detail, getVisitedLinks the clicks. |
| Sender domains /en/api/getSenderDomainList | GET, POST | Domains with SPF and DKIM state, the API view of SMTP settings. |
| Register webhook /en/api/setHook | GET, POST | Registers a callback URL with events and payload encoding. |
All snippets assume a key from Settings › Integration and API and a confirmed sender address. Nothing needs a library beyond an HTTP client.
There is no ping or whoami method, so getLists is the cheapest proof a key works, and it shows why you must inspect the body, not the status code.
curl -sS -X POST https://api.selzy.com/en/api/getLists \
-d format=json -d api_key="$SELZY_API_KEY"
# OK: {"result":[{"id":112233,"title":"Newsletter"}]}
# Bad: {"error":"unknown api_key","code":"invalid_api_key"} <- still HTTP 200import requests
BASE = "https://api.selzy.com/en/api" # unversioned; lang code sits in the path
def call(method, **params):
params.update(format="json", api_key=API_KEY)
data = requests.post(f"{BASE}/{method}", data=params, timeout=30).json()
if "error" in data: # HTTP 200 even on failure
raise RuntimeError(f"{data.get('code')}: {data['error']}")
return data["result"]
sent = call("sendEmail",
email="john@example.com",
sender_name="Acme",
sender_email="hello@yourdomain.com", # confirmed sender only
subject="Your order is on its way",
body="<h1>Hello, World!</h1>",
list_id=112233, # drives the unsubscribe link
track_read=1, track_links=1)
# platform="MySite" <- required on EVERY call if you want a listing
print("queued email_id:", sent[0]["id"])
# Register the outbound hook once at deploy time, not per request.
call("setHook", hook_url="https://example.com/hooks/selzy",
event_format="json_post_gzip", **{"events[]": "email_status"})With one credential and no OAuth, the bridge from MCP tool call to Selzy method is close to mechanical. Extending this skeleton across the method list is copy-and-adjust work.
import os, httpx
from mcp.server.fastmcp import FastMCP
BASE = "https://api.selzy.com/en/api"
KEY = os.environ["SELZY_API_KEY"]
mcp = FastMCP("selzy")
def call(method: str, **params):
# One helper covers the whole API: flat methods, form body, JSON out.
params.update(format="json", api_key=KEY)
data = httpx.post(f"{BASE}/{method}", data=params, timeout=30).json()
if "error" in data:
raise RuntimeError(f"{data.get('code')}: {data['error']}")
return data["result"]
@mcp.tool()
def list_contact_lists() -> list:
"""Return every Selzy contact list with its id and title."""
return call("getLists")
@mcp.tool()
def add_contact(list_id: int, email: str) -> dict:
"""Add or update a contact in a Selzy list."""
return call("subscribe", list_ids=str(list_id), **{"fields[email]": email})
mcp.run()The most common Selzy error online. unisender.com is still trading in 2026 as a Russian-language service for Russia and Belarus, with its own pricing, support and API docs, and never mentions Selzy on its homepage. Both APIs expose identical method names, so a copy-pasted tutorial looks correct while pointing at the wrong company. api.unisender.com is not a deprecated Selzy host to fall back on, it is someone else’s production API.
There is no smtp.selzy.com. Selzy’s relay is UniOne, built by the Selzy team, and it needs its own account: host smtp.eu1.unione.io or smtp.us1.unione.io-match the datacenter your UniOne account was created in, port 587 with STARTTLS or 465 with SSL, username = your UniOne user_id, password = your UniOne API key or a project_api_key. Your Selzy marketing key will not authenticate there, and Selzy domain authentication does not carry over. Full detail on SMTP settings.
Your endpoint gets 3 seconds to return HTTP 200 OK. If it does not, Selzy retries every 10 minutes for 24 hours, then flips the hook to stopped and delivers nothing until you re-register with setHook. A three-second budget makes any synchronous write inside the handler a design error, acknowledge, enqueue, return. Monitor for a quiet event stream: Selzy will not tell you it stopped.
Two limits that break naive integrations. sendEmail enforces a 1,000 messages/day ceiling on new users, 60 calls/minute, a 1 MB message size and a mandatory 60-second gap between two sends to the same recipient, a burst backfill fails rather than queues. And exportContacts is an async job returning a task_uuid you poll with getTaskResult, not a paginated read: limit and offset do nothing.
You cannot simply read the key out of the dashboard. Open Settings › Integration and API, enable API Access, click Show Full, and re-enter your registration password. Self-serve provisioning scripts that assume the key is visible on first load stall here, silently, the user just sees an empty field.
Selzy publishes no public changelog or release-notes page anywhere on selzy.com, the help center runs to 17 categories and none is a changelog. Combined with the unversioned API, changes ship in place with no announcement channel. The entries below are dated findings from our own audit, not vendor announcements.
selzy-dev on GitHub.api.selzy.com only.selzy-dev/selzy-api-skill confirmed as an agent skill wrapping the REST API, with no MCP transport.Watch the official Selzy API reference, and the UniOne API changelog if your sending path runs through the relay. Without a vendor feed, CI contract tests against getLists, subscribe and sendEmail are the cheapest early warning.
Open Settings › Integration and API. API access is off by default, so enable API Access first, then click Show Full beside the key and re-enter your registration password. There is no per-key scoping, a Selzy key is account-wide, so treat it as root and rotate it if it reaches a client-side bundle or a shared log.
https://api.selzy.com/{lang}/api/{method}-in practice https://api.selzy.com/en/api/sendEmail. The lang segment accepts en, ua or ru and sets the language of error messages, not campaign content. It is not a version number. Pin en so error strings stay greppable.
A global ceiling of 1,200 requests per 60 seconds applies per API key or per IP address, underneath per-method caps: sendEmail 60 calls/minute, subscribe and checkEmail 300 requests/60 seconds each, createCampaign 100/60 seconds, sendSms 150 numbers per call. New accounts also carry a 1,000 messages/day ceiling on sendEmail that rises as delivery statistics prove out, a 1 MB message size limit and a 60-second gap between two sends to the same address.
Yes, a first-party event notification system registered with setHook, covering email_status, subscribe, unsubscribe, campaign_status, email_check and user_payment. Use event_format=json_post_gzip, which Selzy recommends over legacy http_get. The critical constraint: your receiver has 3 seconds to return HTTP 200, failures retry every 10 minutes for 24 hours, and the hook is then marked stopped until you re-register it.
No. As of August 2026 there is no official Selzy MCP server and no community-built one, nothing in the Selzy help center, the selzy.com feature pages, the selzy-dev GitHub account or the main MCP directories. The selzy-api-skill repository is an agent skill with no MCP transport. Two paths remain: Zapier MCP, limited to Selzy’s four Zapier operations, or a self-hosted wrapper over api.selzy.com, cheap to build because the API needs one credential.
No, and this is the most expensive mistake you can make with Selzy. The method names are identical because the products share a code heritage, but UniSender is a separate, still-trading company serving Russia and Belarus with its own pricing, support and docs. Your key authenticates only against api.selzy.com; api.unisender.com is not a legacy Selzy host. Check the default base URL of any tutorial or package first. A third name, UniOne, is Selzy’s own SMTP relay and transactional API, again a separate account and key, covered on SMTP settings.
This review follows our email infrastructure testing methodology. We disclose affiliate relationships in our editorial independence policy.