Email marketing platform
EmailOctopus logo

EmailOctopus API + MCP (2026): v2 REST, no official MCP server

EmailOctopus exposes one programmatic surface: a REST API whose current generation is v2, launched October 7, 2024 at https://api.emailoctopus.com with Bearer auth, cursor pagination and RFC 7807 errors. It is a list-and-contact API, not a sending API — no message endpoint, no attachment upload, no SMTP relay — so anything transactional has to leave through a second provider. The agent verdict is equally blunt: no official MCP server in 2026, and every EmailOctopus MCP endpoint you can connect to is a third-party wrapper over that same v2 API.

At a glance

v2
Current API version
v1.6 documented as legacy since October 2024
0
Official SDKs
6 community libraries listed, none verified by the team
None
Official MCP server
4 third-party wrappers over the REST API

MCP integration in 2026

The Model Context Protocol gives an LLM client typed, permissioned access to a SaaS product instead of a scraped dashboard. For an ESP the question is simple: can an agent read your audience, tag contacts and trigger a flow with no glue code? For EmailOctopus in 2026 the answer is yes — but only through someone else’s infrastructure.

i

No first-party MCP server, only third-party wrappers

As of August 2026 nothing on emailoctopus.com, in the knowledge base or in the v2 API documentation announces an MCP server, and there is no public EmailOctopus GitHub organisation to host one. The 4 working endpoints below are all built by automation vendors on top of the public REST API — their tool surface is contact CRUD and tagging, not the full v2 spec. Because EmailOctopus has no send endpoint at all, no MCP server can make it deliver an arbitrary email.

Available MCP servers

Four hosted options expose EmailOctopus over MCP. All authenticate with your own API key or an account link, and all inherit the same ceiling: whatever that vendor’s app already supports.

Why buyers should care

A missing first-party MCP server is no dealbreaker for a newsletter tool, but it changes who owns the integration: a vendor wrapper puts a second SaaS in the credential path with a tool list you do not control. If your agent needs full v2 coverage — batch upserts, campaign reports, automation queueing — wrapping the REST API with a Bearer key is about a hundred lines of code. Check Mailchimp, MailerLite and Sender before assuming an official server exists anywhere in this category.

EmailOctopus API essentials

Everything below applies to v2. v1.6 lived on a different host with a different auth mechanism, is documented as legacy and unmaintained, and its keys are not portable to v2.

Base URLhttps://api.emailoctopus.com RECOMMENDED
Legacy base URLhttps://emailoctopus.com/api/1.6/ (v1.6, legacy)
Current versionv2, live since October 7, 2024
Response formatJSON, shaped as { data, paging } on collections
AuthenticationAuthorization: Bearer eo_xxxxx
PaginationCursor-based, max 100 results per page
Cursor fieldpaging.next.url or paging.next.starting_after
Error formatRFC 7807 problem+json with type, title, detail
Send endpointDoes not exist — no transactional API, no SMTP relay

The resource model is deliberately small: lists, the contacts inside them, the fields and tags on those contacts, read-only campaign reports, and an automation queue you can push a contact into. Campaigns are composed in the dashboard and cannot be created over the API. Internalise that before designing an integration: EmailOctopus is an audience system of record with reporting attached, and dispatch stays inside the product. Teams needing a programmatic send path pair it with Amazon SES or a turnkey relay, exactly as the knowledge base recommends.

Authentication methods

Bearer token (v2)

v2 accepts one credential type: an API key created in account settings and sent as Authorization: Bearer eo_xxxxx. Keys are account-scoped, not list-scoped and not permission-scoped, so a key that can read a list can also delete contacts from it. Rotate by generating a new key and revoking the old one; there is no OAuth flow and no scope selector.

curl -sS -H "Authorization: Bearer eo_xxxxxxxxxxxxxxxx" https://api.emailoctopus.com/lists

Legacy api_key parameter (v1.6)

v1.6 passed the credential as an api_key query-string or body parameter against https://emailoctopus.com/api/1.6/. It still answers for existing keys — there has been no forced migration — but it is unmaintained and it leaks the credential into access logs and proxy caches. Treat any code path still doing this as debt with a deadline you set yourself, because EmailOctopus has published no sunset date.

A v1.6 key will not authenticate against v2

The commonest migration failure. The two generations do not share a key namespace: generate a fresh v2 key, then swap host and auth mechanism together. Reusing the old key against the new base URL returns an auth error that reads like a permissions problem and is not one. Keys carry no scopes, so treat each as a full-account credential — secret manager only, never client-side.

Rate limits

EmailOctopus documents a token-bucket limiter: generous for list maintenance, tight for bulk backfills. Read it as a burst allowance plus a refill rate, not a flat per-minute quota.

LimitValueNotes
Sustained request rate600 requests/minuteEquivalent to 10 requests per second
Burst allowance100 requestsToken bucket of 100 tokens
Refill rate10 tokens/secondBucket refills continuously, not on a fixed window
Over-limit behaviourHTTP 429Connection blocked for up to 60 seconds
Remaining-quota headerX-RateLimiting-RemainingPresent on every response — read it, do not guess
Collection page size100 resultsHard maximum per page on every collection

The workaround for large imports is batching, not parallelism. PUT /lists/{list_id}/contacts/batch upserts many contacts per request, turning a 50,000-contact backfill from a rate-limit fight into a few hundred calls. If you must iterate per contact, cap concurrency at 10 in-flight requests and back off on X-RateLimiting-Remaining before the bucket empties — a 429 is expensive because it blocks the connection for up to a full minute rather than rejecting one call. Note what is not in the table: no hourly send ceiling, no recipients-per-message cap, no maximum message size — sending is not an API operation at all.

SDKs and client libraries

EmailOctopus maintains no official SDKs in any language. Its knowledge base lists 6 community projects and states they are unofficial and untested by the team. The stated position is that the API “plays nicely with any REST client in your chosen language”; EmailOctopus offers developer discounts to people who open-source a library instead of shipping one, and no public EmailOctopus GitHub organisation exists.

LanguagePackageInstallRepo
Node.jsemail-octopusnpm install email-octopuswthomsen
PythonemailoctopusAPIgit clone the repovivekfantain
Rubyemail_octopusgem install email_octopustubbo
SwiftEmailOctopusKitSwift Package Managercaloon
PHPgoran-popovic/email-octopus-phpcomposer requiregoran-popovic
PHP (Laravel)goran-popovic/email-octopus-laravelcomposer requiregoran-popovic

Check v2 support before you install any of these

Several listed libraries predate the October 2024 v2 launch — the Python project is documented as a v1 client. v2 changed host, auth header and pagination shape at once, so a v1-era wrapper does not degrade gracefully; it simply fails to authenticate. Confirm the repo targets api.emailoctopus.com with a Bearer header before taking the dependency.

Notable community SDKs

The two PHP packages from goran-popovic are the most actively maintained and the only ones with a first-class Laravel path. The Ruby gem is the oldest and best read as reference code. On Node and Python, skip the wrapper: two fetch or requests calls cover contact upsert and pagination, which is most of what the API does.

Endpoints reference

The full v2 surface reads in one sitting. The fifteen entries below cover every documented resource plus the two behaviours common to all of them. Canonical reference: emailoctopus.com/api-documentation/v2.

ResourceMethodsDescription
Lists
/lists
GET, POSTList all lists (paginated, 100/page) or create a new list.
List
/lists/{list_id}
GET, PUT, DELETERetrieve, rename or delete a single list.
List contacts
/lists/{list_id}/contacts
GET, POST, PUTPage through contacts, create one, or upsert one by email address.
Contacts batch
/lists/{list_id}/contacts/batch
PUTBatch create-or-update many contacts in a single request.
Contact
/lists/{list_id}/contacts/{contact_id}
GET, PUT, DELETERead, update (fields, tags, status) or delete one contact. Id = MD5 of the lowercased email.
Fields
/lists/{list_id}/fields
POSTCreate a custom field; existing fields are returned inside the list object.
Field
/lists/{list_id}/fields/{tag}
PUT, DELETEUpdate or delete a custom field, addressed by its merge tag.
Campaigns
/campaigns
GETList campaigns with status and metadata. Read-only — composition is dashboard-only.
Campaign
/campaigns/{campaign_id}
GETRetrieve a single campaign’s details.
Summary report
/campaigns/{campaign_id}/reports/summary
GETAggregate sent, opened, clicked, bounced, complained and unsubscribed counts.
Contact reports
/campaigns/{campaign_id}/reports/{event}
GETPer-contact event collections, one per event type.
Links report
/campaigns/{campaign_id}/reports/links
GETClick performance broken down per link URL in the campaign.
Automation queue
/automations/{automation_id}/queue
POSTPush a contact into an automation queue — the only programmatic flow trigger.
Rate-limit headers
(all endpoints)
AllEvery response carries X-RateLimiting-Remaining; 429 means the bucket is empty.
Errors
(all endpoints)
AllRFC 7807 problem+json with type/title/detail. Treat non-2xx as failure.

Code examples

Authentication check with cURL

The cheapest way to confirm a key is a v2 key and not a stale v1.6 one: hit the lists collection and read the status line before debugging anything else.

curl -i -H "Authorization: Bearer eo_xxxxxxxxxxxxxxxx" "https://api.emailoctopus.com/lists?limit=1"

# 200 OK  -> key is valid for v2
# 401/403 -> key is a legacy v1.6 key, regenerate it in Account settings
#
# Useful response header:
#   X-RateLimiting-Remaining: 99

Python: upsert a contact and page the list

import hashlib
import requests

API_KEY = "eo_xxxxxxxxxxxxxxxx"   # v2 key from EmailOctopus > Account settings > API
BASE = "https://api.emailoctopus.com"
LIST_ID = "00000000-0000-0000-0000-000000000000"
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

# 1. Upsert a contact (PUT on the collection = create or update by email)
resp = requests.put(
    f"{BASE}/lists/{LIST_ID}/contacts",
    headers=HEADERS,
    json={
        "email_address": "ada@example.com",
        "fields": {"FirstName": "Ada"},
        "tags": {"vip": True},
        "status": "subscribed",
    },
    timeout=30,
)
resp.raise_for_status()
contact = resp.json()

# contact_id is the MD5 of the LOWERCASED email address
print(contact["id"] == hashlib.md5(b"ada@example.com").hexdigest())

# 2. Page through every contact using the cursor in `paging.next`
url = f"{BASE}/lists/{LIST_ID}/contacts?limit=100"
while url:
    page = requests.get(url, headers=HEADERS, timeout=30).json()
    for c in page["data"]:
        print(c["email_address"], c["status"])
    url = (page.get("paging") or {}).get("next", {}).get("url")

# NOTE: no send/message endpoint exists - campaigns are sent from the dashboard.

Node.js: upsert, trigger an automation, handle 429

// npm i node-fetch@3   (or use global fetch on Node 18+)
const API_KEY = process.env.EMAILOCTOPUS_API_KEY; // v2 key
const BASE = 'https://api.emailoctopus.com';
const LIST_ID = '00000000-0000-0000-0000-000000000000';

const headers = {
  Authorization: `Bearer ${API_KEY}`,
  'Content-Type': 'application/json',
};

// 1. Upsert a contact
const res = await fetch(`${BASE}/lists/${LIST_ID}/contacts`, {
  method: 'PUT',
  headers,
  body: JSON.stringify({
    email_address: 'ada@example.com',
    fields: { FirstName: 'Ada' },
    tags: { vip: true },
    status: 'subscribed',
  }),
});
if (!res.ok) throw new Error(`EmailOctopus ${res.status}: ${await res.text()}`);

// 2. Trigger an automation for that contact
await fetch(`${BASE}/automations/AUTOMATION_ID/queue`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ contacts: [{ email_address: 'ada@example.com' }] }),
});

// 3. Cursor pagination, with 429 back-off
let url = `${BASE}/lists/${LIST_ID}/contacts?limit=100`;
while (url) {
  const r = await fetch(url, { headers });
  if (r.status === 429) { await new Promise(s => setTimeout(s, 60000)); continue; }
  const page = await r.json();
  url = page.paging?.next?.url ?? null;
}

Common gotchas

There is no send endpoint, and no SMTP relay behind it either

The knowledge base is explicit that all emails must be created and sent through the dashboard, and v2 has no message resource to compensate. EmailOctopus Connect looks like the exception and is not: its setup guide asks for an AWS IAM access key and secret with the AmazonSESFullAccess policy, and EmailOctopus calls the SES API on your behalf. “EmailOctopus SMTP credentials” are always Amazon SES credentials from someone’s own AWS account. Plan transactional mail on Amazon SES or a dedicated relay from day one.

v2 keys and v1.6 keys are different objects

v2 launched on a new host (api.emailoctopus.com, Bearer header) while v1.6 lived at emailoctopus.com/api/1.6/ with an api_key parameter. Legacy keys keep working there but will not authenticate against v2, so migrating means regenerating the key, changing the host, moving the credential into a header and rewriting pagination — four changes that ship together. No v1 sunset date is published, which makes it easy to defer indefinitely and get caught during an unrelated incident.

Pagination is cursor-based, so offset logic silently breaks

Collections cap at 100 results and return a paging.next holding both a ready-made url and a starting_after cursor. Follow the URL instead of incrementing a page number: there is no total count to loop against, and an invented offset returns page one repeatedly. The loop ends when paging.next is absent, not when a page comes back short.

Contact ids are MD5 hashes, and case matters

A contact’s id is the MD5 hash of its lowercased email address. Hashing the address as typed produces a valid-looking id that resolves to nothing, and the 404 reads like a missing contact rather than a normalisation bug. Lowercase before hashing everywhere, including in the job that reconciles your warehouse against the list. Mailchimp uses the same convention, so ported code usually survives.

Attachments do not exist anywhere in the product

EmailOctopus does not support file attachments at all; the knowledge base tells you to host the file elsewhere and link to it. So there is no attachment API, no MIME upload path and no published maximum message size. A migration plan that assumed you could attach a PDF invoice fails at the product level, not the API level.

Deprecations and changelog

  • July 22, 2026 — COO Tom Evans publishes an open letter, “why our free plan isn’t going anywhere”, committing to keep the free Starter tier.
  • October 10, 2025 — Automation templates released. Relevant to API users because POST /automations/{automation_id}/queue is the only programmatic trigger.
  • July 3, 2025 — “Contacts” replaces the Lists screen as the primary audience view. API resource names stay /lists.
  • March 31, 2025 — Date-field automation triggers released; flows can start from custom date fields such as birthdays.
  • October 7, 2024 — API v2 launched at api.emailoctopus.com with Bearer auth, cursor pagination and RFC 7807 errors. v1 keeps working but is documented as legacy; keys must be regenerated.
  • August 30, 2019 — EmailOctopus drops the Amazon SES requirement for new signups, moving to its own partnered infrastructure. Connect stays available for teams that prefer their own SES account — the split still causing SMTP confusion in 2026.

Product updates: emailoctopus.com/blog/category/product-updates. API changes are announced in the v2 documentation.

Frequently asked questions

What is the current EmailOctopus API version, v1.6 or v2?

v2, live since October 7, 2024 at https://api.emailoctopus.com. v1.6 at https://emailoctopus.com/api/1.6/ is documented as legacy and no longer actively maintained. Existing v1 keys still work there — no forced migration, no announced sunset date — but nothing new is added, and the credential travels in the query string.

How do I authenticate with the EmailOctopus API v2?

Generate a key in Account settings and send it as Authorization: Bearer eo_xxxxx. There is no OAuth flow and no per-key scoping, so every key is a full-account credential — keep it server-side. Critically, a v1.6 key will not authenticate against v2. A 401 or 403 straight after changing the base URL is almost always this.

What are the EmailOctopus API rate limits?

A token bucket of 100 tokens refilling at 10 per second600 requests per minute sustained with a 100-request burst. Exceeding it returns HTTP 429 and blocks the connection for up to a minute, so it is worth avoiding rather than retrying into. Read X-RateLimiting-Remaining on every response, and use PUT /lists/{list_id}/contacts/batch for imports.

How do I add or update a contact with the EmailOctopus API?

Send PUT /lists/{list_id}/contacts with an email_address plus optional fields, tags and status. PUT on the collection is an upsert keyed on email — safe to replay, and the right call for a sync job. To address an existing contact use /lists/{list_id}/contacts/{contact_id}, where contact_id is the MD5 of the lowercased email address; skipping the lowercase step is the classic source of phantom 404s.

Does EmailOctopus have an official SDK for Python, PHP or Node.js?

No. EmailOctopus maintains zero official SDKs and says its API “plays nicely with any REST client in your chosen language”. The knowledge base lists 6 community libraries (Node.js, Python, Ruby, Swift, two PHP packages) with the explicit caveat that they are unofficial and untested. Several predate v2 — the Python one targets v1 — so check the repo for api.emailoctopus.com and Bearer auth first. A small in-house client is usually safer.

Is there an official EmailOctopus MCP server for AI agents?

No. As of August 2026 there is no first-party MCP server, no AI connector and no public EmailOctopus GitHub organisation. Four third-party wrappers work today — Zapier MCP, Pipedream, Composio and viaSocket — but each exposes only its own connector’s actions, mostly contact CRUD and tagging, not the full v2 surface. And with no send endpoint, no MCP server can make an agent send an arbitrary email through EmailOctopus.

Changelog (recent)

  • 2026-07-22 COO Tom Evans publishes an open letter, why our free plan is not going anywhere, committing to keep the free Starter tier.
  • 2025-10-10 Automation templates released. Relevant to API users because POST /automations/{automation_id}/queue is the only programmatic flow trigger.
  • 2025-07-03 Contacts replaces the Lists screen as the primary audience view in a dashboard redesign; the API resource stays /lists.
AAlaa Touil RRabeb How we test →

This review follows our email infrastructure testing methodology. We disclose affiliate relationships in our editorial independence policy.