Mailjet runs two API versions side by side and they are not interchangeable: v3 is the REST platform for every non-send resource, v3.1 exists only for the Send API. Authentication is HTTPS Basic Auth with the same key pair that powers the SMTP settings, and Mailjet ships 7 official SDKs plus an EU-resident API host at api.eu.mailjet.com. On MCP it is ahead of the field — a first-party server since September 30, 2025 — but read-only, so an agent can interrogate your account and cannot send from it.
MCP is how an AI client reaches into a SaaS account without a bespoke plugin. For an ESP the question is not whether a wrapper exists on GitHub, but whether the vendor ships one and what it may do.
Mailjet published its own MCP server on September 30, 2025, Apache-2.0, with the write-up on October 31, 2025. It speaks stdio, authenticates with MAILJET_API_KEY in api_key:secret_key form, and routes to EU infrastructure when you set MAILJET_API_REGION=eu. The help centre states the constraint plainly — read only, for exploring contacts, campaigns, segments, statistics and workflows.
Three of the four below are Mailjet’s own material; the fourth is Zapier’s hosted endpoint, the only route offering write actions.
Tools are generated at runtime from the bundled OpenAPI spec, so coverage tracks the REST API. npx -y @mailjet/mailjet-mcp-server.
Help-centre article with the exact claude_desktop_config.json snippet for macOS and Windows.
Product post framing the server as read-only to protect production, pitching natural-language querying of open and bounce rates.
Hosted endpoint exposing Mailjet actions through Zapier’s connector. Because it drives Zaps it can write — the workaround when an agent must act.
Most ESPs in this bracket still have no first-party MCP server, so this is a real differentiator rather than a checkbox. The read-only boundary is a feature if your risk model says an LLM must never touch production sending, and a blocker if you budgeted for agent-driven campaigns — in which case pair the official server for analysis with a direct Send API path for execution. And MAILJET_API_REGION=eu lets an EU buyer wire an AI client to their account without the request leaving EU infrastructure, the same reason Mailjet wins on residency against SendGrid.
The version split is the first thing to internalise: everything that is not a send — contacts, lists, campaigns, templates, statistics, senders, DNS, webhooks, parse routes — lives at /v3/REST/ and always will.
| Base URL (global) | https://api.mailjet.com |
|---|---|
| Base URL (EU residency) | https://api.eu.mailjet.com GDPR |
| REST resources | /v3/REST/{resource} |
| Send API (current) | POST /v3.1/send |
| Send API (legacy, still live) | POST /v3/send |
| Response format | JSON, with Count, Total and a Data array on REST reads |
| Pagination | Offset-based: Limit (default 10, max 1000), Offset, Sort |
| Attachment ceiling | 15 MB per message, v3 and v3.1 alike |
| Webhooks | Event API, 7 event types, batched arrays |
Limit defaults to 10, so a first call to /v3/REST/contact returns ten records and an integrator concludes the account is nearly empty. Raise it to 1000, read Total from the envelope, and increment Offset until you have the lot. There is no cursor pagination, so the client owns the loop. Mailjet publishes no HTTP request timeout, so set your own client-side deadline.
Every Email API endpoint authenticates with HTTPS Basic Auth: username is your API Key (public), password your Secret Key (private), both under Account settings › SMTP and SEND API settings. The relay at in-v3.mailjet.com expects the same pair, so rotating a key breaks both transports at once — plan it with your SMTP settings open. All seven SDKs read it from MJ_APIKEY_PUBLIC and MJ_APIKEY_PRIVATE.
The /v3/REST/apitoken resource issues short-lived tokens for embedding Mailjet UI components in your own product. Scoped to that job — not a replacement for Basic Auth, and not a way to give a partner limited access.
Another key from /v3/REST/apikey is how Mailjet does sub-accounts: separating transactional from marketing, or ring-fencing staging. Deactivate with IsActive: false on a PUT rather than deleting; per-key lifetime usage is at /v3/REST/apikeytotals.
Mailjet has no OAuth 2.0 flow and no scoped-token model — a key is all-or-nothing on its account. The only way to narrow access is a second key, and that key brings its own contact database: contacts, lists, segments and templates are not shared between keys, including the main one, and there is no merge afterwards. Decide the topology before you import contacts, and use a MetaSender so a sender validated once is reusable across keys.
Mailjet is unusually sparse here. The honest table has more blanks than most competitors would like; what follows is only what Mailjet publishes.
| Limit | Documented value | Notes |
|---|---|---|
| Attachment / message size | 15 MB | The only hard size ceiling in either Send API guide. |
| Test Mode send rate | 10 emails/hour | New, unreviewed accounts until they leave Test Mode. |
| Free plan volume | 6,000/month, 200/day | Plus a 1,000-contact ceiling since October 6, 2025. |
| Daily Send Limit per key | 5x subscription limit | Paid accounts; Contract defaults to Unlimited. Settable per key; 0 blocks it. |
| Pagination page size | 1000 max, 10 default | Per request on /v3/REST/ resources. |
| API requests per minute, steady-state emails/hour, recipients per message, concurrent SMTP connections | Not published | None of these four appear in official documentation. The developer-portal rate-limits page renders client-side and exposes no figure; new customers sit under an unpublished hourly cap. |
The workaround is empirical: ramp deliberately, instrument for HTTP 429 and SMTP deferrals, and back off exponentially. Mailjet tells new customers to demonstrate good performance over at least one week before the initial hourly limit lifts, which gives the shape of the ramp if not the number. If you need a published requests-per-second figure, Postmark and MailerSend are more forthcoming.
Seven wrappers are published under the github.com/mailjet organisation. All share one design: construct a client with the key pair, set the API version at client level, then call resources.
| Language | Package | Install | Repo |
|---|---|---|---|
| Python | mailjet-rest | pip install mailjet-rest | apiv3-python |
| Node.js | node-mailjet | npm install node-mailjet | apiv3-nodejs |
| PHP | mailjet/mailjet-apiv3-php | composer require mailjet/mailjet-apiv3-php | apiv3-php |
| Ruby | mailjet | gem install mailjet | mailjet-gem |
| Java | com.mailjet:mailjet-client | Maven or Gradle dependency | apiv3-java |
| Go | mailjet-apiv3-go | go get github.com/mailjet/mailjet-apiv3-go | apiv3-go |
| C# / .NET | Mailjet.Api | dotnet add package Mailjet.Api | apiv3-dotnet |
Python and Node.js show the most recent movement: the Python README documents Python 3.10+ tested to 3.14, and node-mailjet v6.0.11 shipped on October 27, 2025 with TypeScript types. PHP is the transport underneath Mailjet’s WordPress and PrestaShop plugins. Java, Go and .NET remain functional but see less traffic — for a new integration choose Python or Node.js.
Because the API is Basic Auth over JSON with no signing step, the framework ecosystem reaches Mailjet through generic mail transports rather than dedicated third-party SDKs: Symfony and Laravel through their mailer abstractions, Rails through the official gem’s ActionMailer hook. For anything else, write forty lines against the REST endpoints rather than adopting an unofficial wrapper that may be abandoned.
The fifteen resources below carry almost all real integration traffic. Full reference: Mailjet’s Email API reference and the open-source api-documentation repo, often easier to read because the portal renders client-side.
| Resource | Methods | Description |
|---|---|---|
| Send API v3.1 /v3.1/send | POST | Current transactional send. A Messages array of From, To, Subject, TextPart, HTMLPart, TemplateID and Variables. Supports partial success. |
| Send API v3 (legacy) /v3/send | POST | Older flat payload with FromEmail, Text-part, Html-part and either Recipients or To/Cc/Bcc. Source of most version confusion. |
| Contact /v3/REST/contact | GET, POST, PUT | Individual contacts. PUT with IsExcludedFromCampaigns toggles the global exclusion list. |
| Bulk contacts /contact/managemanycontacts | POST | Async create or update of many contacts. Returns a job reference to poll. |
| Contact list /v3/REST/contactslist | GET, POST, PUT, DELETE | Mailing lists. Creation needs only a Name. |
| Add contact to list /contactslist/{id}/managecontact | POST | Add, remove or unsubscribe one contact on a list, creating it in the same call. |
| Contact metadata /v3/REST/contactmetadata | GET, POST, PUT, DELETE | Custom property schema; /contactdata/{id} holds per-contact values. |
| CSV import /v3/REST/csvimport | GET, POST | Bulk import job, and the bulk-exclusion path — which must omit ContactsListID. |
| Campaign draft /v3/REST/campaigndraft | GET, POST, PUT | Campaign lifecycle: detailcontent, test, send, schedule (ISO 8601), DELETE to cancel. |
| Template /v3/REST/template | GET, POST, PUT, DELETE | Reusable templates; /template/{id}/detailcontent holds the body. Default headers are overridable per send. |
| Sender and domain /v3/REST/sender | GET, POST, PUT, DELETE | Register an address or whole domain, then activate via /sender/{id}/validate; /metasender reuses it across keys. |
| DNS check /v3/REST/dns/{id_or_domain} | GET, POST | GET returns the SPF and DKIM values expected; POST on /check re-validates after DNS propagates. |
| Event callback URL /v3/REST/eventcallbackurl | GET, POST, PUT, DELETE | Event API callback endpoints, one per event type, with an isBackup fallback URL. |
| Parse route /v3/REST/parseroute | GET, POST, PUT, DELETE | Inbound routing; only the webhook Url is mandatory. Set Email to receive on your own domain. |
| Message and history /v3/REST/message | GET | Per-message delivery record; /messagehistory/{id} returns the full event trail. |
Reporting sits in a family of resources rather than one endpoint: /v3/REST/statcounters is the general aggregator, while geostatistics, openinformation, clickstatistics, bouncestatistics and statistics/recipient-esp break the data out by country, user agent, click URL and mailbox provider.
# pip install mailjet-rest
import os
from mailjet_rest import Client
# version='v3.1' selects the current Send API. Omit it and you get the v3
# endpoint, which expects a different payload shape entirely.
mailjet = Client(
auth=(os.environ['MJ_APIKEY_PUBLIC'], os.environ['MJ_APIKEY_PRIVATE']),
version='v3.1',
)
data = {'Messages': [{
'From': {'Email': 'no-reply@yourdomain.com', 'Name': 'Your App'},
'To': [{'Email': 'customer@example.com', 'Name': 'Customer'}],
'Subject': 'Your receipt',
'TextPart': 'Thanks for your order.',
'HTMLPart': '<h3>Thanks for your order.</h3>',
'CustomID': 'order-1234',
}]}
result = mailjet.send.create(data=data)
print(result.status_code, result.json())
# EU residency: add api_url='https://api.eu.mailjet.com/' to Client().// npm install node-mailjet
const Mailjet = require('node-mailjet');
const mailjet = Mailjet.apiConnect(
process.env.MJ_APIKEY_PUBLIC,
process.env.MJ_APIKEY_PRIVATE,
);
// { version: 'v3.1' } is required - the default is v3, whose payload
// uses FromEmail / Html-part, not From / HTMLPart.
mailjet
.post('send', { version: 'v3.1' })
.request({
Messages: [{
From: { Email: 'no-reply@yourdomain.com', Name: 'Your App' },
To: [{ Email: 'customer@example.com', Name: 'Customer' }],
Subject: 'Your receipt',
TextPart: 'Thanks for your order.',
HTMLPart: '<h3>Thanks for your order.</h3>',
CustomID: 'order-1234',
}],
})
.then((result) => console.log(result.body))
.catch((err) => console.log(err.statusCode, err.message));Mailjet groups every event of the last second for one webhook URL into a single POST, so the body is an array of mixed event types. Iterate, always.
app.post('/webhooks/mailjet', express.json(), (req, res) => {
// ALWAYS an array. A single-object handler passes in testing and
// breaks the moment batching kicks in under real traffic.
const events = Array.isArray(req.body) ? req.body : [req.body];
for (const e of events) {
// e.event: sent | open | click | bounce | spam | blocked | unsub
enqueue({
type: e.event,
email: e.email,
at: new Date(e.time * 1000),
messageId: e.MessageID,
correlation: e.CustomID, // set at send time
});
}
// Return 200 fast. A non-200 is retried every 30 seconds for 24 hours.
res.sendStatus(200);
});The single most common Mailjet integration failure. /v3/send takes a flat body with FromEmail, Text-part, Html-part and either Recipients or To/Cc/Bcc; /v3.1/send takes a Messages array using From, To, TextPart and HTMLPart, with Vars renamed Variables. In the SDKs the version is a client-level setting, so forgetting it posts a v3.1-shaped payload to the v3 endpoint and returns a property-validation error rather than a version error. There is a second trap inside v3: recipients in To see every other recipient, while recipients in Recipients each get a private message — pick the wrong key and you leak your whole list.
Mailjet groups all events of the last second for one webhook URL, so your endpoint receives a JSON array mixing sent, open, click, bounce, spam, blocked and unsub. Handlers written against a single object work in low-volume testing, then break the moment real traffic triggers batching. Fail to return 200 and Mailjet retries every 30 seconds, stopping 24 hours after the first failure — a handler that errors under load spends a day duplicating itself. Correlate with CustomID and Payload.
Mailjet’s documentation is explicit that contacts and lists are not shared between API keys, including the main one. Teams that add a second key to split transactional from marketing, or to isolate staging, find afterwards that lists, segments and templates did not follow, and there is no merge — you re-import. Sender validation has the same problem, which is why MetaSender exists.
Exclusion is an account-level status, not a per-list one, and excluded contacts still receive all transactional email — including messages carrying a campaign tag. It is not a suppression list for transactional mail. When bulk-excluding through POST /v3/REST/csvimport the payload must not contain ContactsListID; including it returns MJ08 Property ContactsList is invalid. For one contact, set IsExcludedFromCampaigns via PUT on /v3/REST/contact/{ID_OR_EMAIL}.
Nothing here is formally deprecated: /v3/send remains live with no published sunset date, but new transactional work belongs on /v3.1/send. Track changes at Mailjet’s official release notes.
They are not two generations of one API. v3 is the whole REST platform — contacts, lists, campaigns, templates, statistics, senders, DNS, webhooks — and it is not going anywhere. v3.1 exists only for sending: there is no /v3.1/REST/. /v3/send is flat, with FromEmail and Html-part; /v3.1/send wraps everything in a Messages array with From and HTMLPart, renaming Vars to Variables.
HTTPS Basic Auth: username is your API Key (public), password your Secret Key (private), both under Account settings › SMTP and SEND API settings. The SDKs read them from MJ_APIKEY_PUBLIC and MJ_APIKEY_PRIVATE. There is no OAuth flow and no scoped token: a key is all-or-nothing, and the only isolation mechanism is a second key with its own contact database. The same pair authenticates the relay at in-v3.mailjet.com — see SMTP settings.
Register endpoints on /v3/REST/eventcallbackurl, or in the UI under Account settings › Event Tracking, one entry per event type with an optional isBackup URL. Mailjet fires seven event types — sent, open, click, bounce, spam, blocked and unsub — for transactional and marketing mail alike. Events from the last second are batched into one POST, so your handler receives a JSON array. Return HTTP 200, or Mailjet retries every 30 seconds for 24 hours.
Yes, and it is real inbound routing with MIME parsing, not a forwarding hack. Create a route with POST /v3/REST/parseroute; the only mandatory property is the webhook Url. Mailjet allocates an address at parse-in1.mailjet.com, or you receive on your own domain by verifying it, adding an MX record to parse.mailjet.com. and setting the route’s Email. Mail is POSTed as JSON with sender, recipient, subject, headers, both body parts, a SpamAssassinScore and Base64 attachments.
Yes for the API: https://api.eu.mailjet.com is declared in Mailjet’s official OpenAPI specification and selected in the official MCP server with MAILJET_API_REGION=eu. In Python pass api_url='https://api.eu.mailjet.com/'; with curl, swap the host. The SMTP relay is different: Mailjet publishes one global hostname, in-v3.mailjet.com, with no EU-specific SMTP endpoint documented. This EU-first posture is Mailjet’s clearest edge over SendGrid, and shared ground with Brevo.
Yes — one of the earlier first-party ESP MCP releases, dated September 30, 2025, Apache-2.0, installable with npx -y @mailjet/mailjet-mcp-server. Two caveats decide whether it fits. It is read-only by design, scoped to contacts, campaigns, segments, statistics and workflows; agentic sending needs the Send API or Zapier MCP. And its tool surface is generated at runtime from Mailjet’s OpenAPI spec, so there is no fixed published tool list — coverage mirrors the /v3/REST/ resources.
This review follows our email infrastructure testing methodology. We disclose affiliate relationships in our editorial independence policy.