Mailjet V3 API wrapper
Project description
Official Mailjet Python Wrapper
Table of contents
- Overview
- Compatibility
- Requirements
- Installation
- Authentication
- Quick Start
- Usage
- Logging & Debugging
- Performance & Architecture
- Security Guardrails
- Request examples
- Deprecation Warnings
- Type Hinting
- License
- Contribute
- Contributors
Overview
Welcome to the Mailjet official Python API wrapper!
Check out all the resources and Python code examples in the official Mailjet Documentation.
Compatibility
This library mailjet_rest officially supports the following Python versions:
- Python >= 3.10, < 3.15
Requirements
- Build backend:
setuptools,wheel,setuptools-scm - Runtime:
requests >= 2.32.5
Test dependencies
For running test you need pytest >=7.0.0 at least.
Make sure to provide the environment variables from Authentication.
Installation
pip install
Create a virtual environment and install the wrapper:
python -m venv venv
source venv/bin/activate
pip install mailjet-rest
git clone & pip install locally
Use the below code to install the wrapper locally by cloning this repository:
git clone https://github.com/mailjet/mailjet-apiv3-python
cd mailjet-apiv3-python
pip install .
conda & make
Use the below code to install it locally by conda and make on Unix platforms:
make install
For development
Using conda
on Linux or macOS:
- A basic environment with a minimum number of dependencies:
make dev
conda activate mailjet
- A full dev environment:
make dev-full
conda activate mailjet-dev
Management script
We provide a universal management script (manage.sh) to simplify local development, testing, and linting.
# 1. Setup the conda environment and pre-commit hooks
./manage.sh env_setup
conda activate mailjet-dev
# 2. Run the test suite (Unit + Integration)
./manage.sh test_all
# 3. Run the performance profilers
./manage.sh perf_bench
# 4. Format and lint the code
./manage.sh format
./manage.sh lint
Authentication
The Mailjet Email API uses your API and Secret keys for authentication. Grab and save your Mailjet API credentials securely in your environment variables.
export MJ_APIKEY_PUBLIC='your api key' # pragma: allowlist secret
export MJ_APIKEY_PRIVATE='your api secret' # pragma: allowlist secret
export MJ_CONTENT_TOKEN='your_bearer_token' # Optional, for Content API v1
Quick Start
Best Practice: Use the Mailjet Client as a Context Manager (with statement) to automatically pool and close underlying TCP connections, preventing resource leaks.
import os
from mailjet_rest import Client
api_key = os.environ.get("MJ_APIKEY_PUBLIC", "")
api_secret = os.environ.get("MJ_APIKEY_PRIVATE", "")
with Client(auth=(api_key, api_secret), version="v3.1") as mailjet:
data = {
"Messages": [
{
"From": {"Email": "pilot@mailjet.com", "Name": "Mailjet Pilot"},
"To": [{"Email": "passenger1@mailjet.com", "Name": "Passenger 1"}],
"Subject": "Your email flight plan!",
"TextPart": "Welcome to Mailjet! May the delivery force be with you!",
}
]
}
result = mailjet.send.create(data=data)
print(result.status_code)
(Note:
Note If you choose not to use the context manager, you should manually call mailjet.close() when your application shuts down).
Advanced Configuration
You can pass configuration overrides directly when initializing the Client or during individual API calls:
# Set custom base URL, timeout, and API version
mailjet = Client(
auth=(api_key, api_secret),
version="v3.1",
api_url="https://api.us.mailjet.com/",
timeout=30,
)
# Override timeout for a single, heavy request
result = mailjet.contact.get(timeout=60)
API Versioning
The Mailjet API is spread among distinct versions:
v3- The Email APIv3.1- Email Send API v3.1, which is the latest version of our Send APIv1- Content API (Templates, Blocks, Images)
Since most Email API endpoints are located under v3, it is set as the default one and does not need to be specified when making your request.
For the others you need to specify the version using version. For example, if using Send API v3.1:
mailjet = Client(auth=(api_key, api_secret), version="v3.1")
For additional information refer to our API Reference.
Base URL
The default base domain name for the Mailjet API is api.mailjet.com. You can modify this base URL by setting a value for api_url in your call:
mailjet = Client(auth=(api_key, api_secret), api_url="https://api.us.mailjet.com/")
If your account has been moved to Mailjet's US architecture, the URL value you need to set is https://api.us.mailjet.com.
Usage
Error Handling
The client safely wraps network-level exceptions. Standard HTTP errors (like 404 Not Found or 400 Bad Request) do not raise exceptions; they return the requests.Response object directly so you can inspect status_code and .json().
from mailjet_rest import CriticalApiError, TimeoutError, ApiError
try:
result = mailjet.contact.get()
if result.status_code != 200:
print(f"API Error: {result.status_code} - {result.text}")
except TimeoutError:
print("The request to the Mailjet API timed out.")
except CriticalApiError as e:
print(f"Network connection failed: {e}")
Logging & Debugging
The SDK integrates seamlessly with Python's standard logging library and features Smart Telemetry.
If you pass identifiers like CustomID, Campaign, or TemplateID in your payload, the SDK automatically extracts them and injects a Trace context into your logs.
This allows you to easily correlate local application errors with your Mailjet Dashboard analytics.
import logging
from mailjet_rest import Client
logging.getLogger("mailjet_rest.client").setLevel(logging.DEBUG)
logging.basicConfig(format="%(levelname)s - %(message)s")
with Client(auth=(api_key, api_secret), version="v3.1") as mailjet:
# Adding 'CustomID' enables Smart Tracing in the console logs
mailjet.send.create(
data={
"Messages": [
{
"From": {"Email": "test@test.com"},
"To": [{"Email": "user@test.com"}],
"CustomID": "Promo_Black_Friday",
}
]
}
)
Console output will feature: DEBUG - Sending Request: POST ... | Trace: [CustomID=Promo_Black_Friday]
IDE Autocompletion & DX
Because the SDK utilizes dynamic URL dispatching (__getattr__), to prevent "Magic Method Traps" (accidentally dispatching internal Python methods), the SDK includes strict poka-yoke guardrails.
Attempting to access private attributes or removed properties (like client.auth) will safely throw an explicit AttributeError instead of a ghost API request.
URL path
According to python special characters limitations we can't use slashes / and dashes - which is acceptable for URL path building. Instead, python client uses another way for path building. You should replace slashes / by underscore _ and dashes - by capitalizing next letter in path.
For example, to reach statistics/link-click path you should call statistics_linkClick attribute of python client.
# GET `statistics/link-click`
mailjet = Client(auth=(api_key, api_secret))
filters = {"CampaignId": "xxxxxxx"}
result = mailjet.statistics_linkClick.get(filters=filters)
print(result.status_code)
print(result.json())
For the Content API (v1), sub-actions will be correctly routed using slashes (e.g. contents/lock). Additionally, the SDK maps the data_images resource specifically to /v1/data/images to support media uploads.
# GET '/v1/data/images'
mailjet = Client(auth=(api_key, api_secret), version="v1")
result = mailjet.data_images.get()
Performance & Architecture
The Mailjet SDK v1.6.0+ has been heavily optimized for high-concurrency and memory-constrained environments (like AWS Lambda).
It utilizes __slots__ for memory density, immutable MappingProxyType headers for zero-allocation merging, and O(1) dynamic endpoint caching.
For a detailed breakdown of our nanosecond routing benchmarks and instructions on how to profile the SDK, please read our Performance & Architecture Guide.
Security Guardrails
The SDK includes active protections against common API vulnerabilities:
- SSRF & Open Redirects: Hard-disabled automatic redirects and enforced strict hostname validation.
- CRLF Injection: Native string evaluation blocks header injection attempts via compromised Bearer tokens or custom headers.
- PEP 578 Audit Hooks: The SDK emits native Python audit events (
sys.audit) for all outbound network egress and explicitly warns if TLS verification is bypassed.
See our SECURITY.md for our vulnerability disclosure policy and supported versions.
Request examples
Full list of supported endpoints
[!IMPORTANT]
This is a full list of supported endpoints this wrapper provides samples
Executable README (Smoke Test)
Want to test all these examples at once? We provide an executable script that dynamically creates, tests, and safely cleans up all resources mentioned in this document. It's a great way to verify your API credentials and network access.
Simply run:
python samples/smoke_readme_runner.py
Send API (v3.1)
Send a basic email
from mailjet_rest import Client
import os
api_key = os.environ.get("MJ_APIKEY_PUBLIC", "")
api_secret = os.environ.get("MJ_APIKEY_PRIVATE", "")
mailjet = Client(auth=(api_key, api_secret), version="v3.1")
data = {
"Messages": [
{
"From": {"Email": "pilot@mailjet.com", "Name": "Mailjet Pilot"},
"To": [{"Email": "passenger1@mailjet.com", "Name": "Passenger 1"}],
"Subject": "Your email flight plan!",
"TextPart": "Dear passenger 1, welcome to Mailjet!",
"HTMLPart": "<h3>Dear passenger 1, welcome to Mailjet!</h3>",
}
]
}
result = mailjet.send.create(data=data)
print(result.status_code)
print(result.json())
Send an email using a Mailjet Template
When using TemplateLanguage, ensure that you pass a standard Python dictionary to the Variables parameter.
mailjet = Client(auth=(api_key, api_secret), version="v3.1")
data = {
"Messages": [
{
"From": {"Email": "pilot@mailjet.com", "Name": "Mailjet Pilot"},
"To": [{"Email": "passenger1@mailjet.com", "Name": "passenger 1"}],
"TemplateID": 1234567, # Put your actual Template ID here
"TemplateLanguage": True,
"Subject": "Your email flight plan!",
"Variables": {"name": "John Doe", "custom_data": "Welcome aboard!"},
}
]
}
result = mailjet.send.create(data=data)
Standard REST Actions (GET, POST, PUT, DELETE)
POST (Create)
Simple POST request
# Create a new contact
data = {"Email": "Mister@mailjet.com"}
result = mailjet.contact.create(data=data)
print(result.json())
Using actions
# Manage the subscription status of a contact to multiple lists
id_ = "$ID"
data = {
"ContactsLists": [
{"ListID": "$ListID_1", "Action": "addnoforce"},
{"ListID": "$ListID_2", "Action": "addforce"},
]
}
result = mailjet.contact_managecontactslists.create(id=id_, data=data)
print(result.json())
GET Request
Retrieve all objects
# Retrieve all contacts
result = mailjet.contact.get()
print(result.json())
GET (Read one)
# Retrieve a specific contact ID
id_ = "Contact_ID"
result = mailjet.contact.get(id=id_)
print(result.json())
Using filtering
# Retrieve contacts that are not in the campaign exclusion list
filters = {
"limit": 40,
"offset": 50,
"sort": "Email desc",
"IsExcludedFromCampaigns": "false",
}
result = mailjet.contact.get(filters=filters)
print(result.json())
Using pagination
Some requests (for example GET /contact) has limit, offset and sort query string parameters. These parameters could be used for pagination.
limit int Limit the response to a select number of returned objects. Default value: 10. Maximum value: 1000
offset int Retrieve a list of objects starting from a certain offset. Combine this query parameter with limit to retrieve a specific section of the list of objects. Default value: 0
sort str Sort the results by a property and select ascending (ASC) or descending (DESC) order. The default order is ascending. Keep in mind that this is not available for all properties. Default value: ID asc
Next example returns 40 contacts starting from 51th record sorted by Email field descendally:
filters = {
"limit": 40,
"offset": 50,
"sort": "Email desc",
}
result = mailjet.contact.get(filters=filters)
print(result.json())
PUT (Update / Patch specific fields)
A PUT request in the Mailjet API will work as a PATCH request - the update will affect only the specified properties. The other properties of an existing resource will neither be modified, nor deleted. It also means that all non-mandatory properties can be omitted from your payload.
# Update the contact properties for a contact
id_ = "$CONTACT_ID"
data = {
"Data": [
{"Name": "first_name", "value": "John"},
{"Name": "last_name", "value": "Smith"},
]
}
result = mailjet.contactdata.update(id=id_, data=data)
print(result.json())
DELETE (Returns 204 No Content)
Upon a successful DELETE request the response will not include a response body, but only a 204 No Content response code.
# Delete an email template
id_ = "Template_ID"
result = mailjet.template.delete(id=id_)
print(result.json())
Email API Ecosystem (Webhooks, Parse API, Segmentation, Stats)
Webhooks (Real-time Event Tracking)
You can subscribe to real-time events (open, click, bounce, etc.) by configuring a webhook URL using the eventcallbackurl resource.
data = {
"EventType": "open",
"Url": "https://www.mydomain.com/webhook",
"Status": "alive",
}
result = client.eventcallbackurl.create(data=data)
Parse API (Receive Inbound Emails)
The Parse API routes incoming emails sent to a specific domain to your custom webhook.
data = {"Url": "https://www.mydomain.com/mj_parse.php"}
result = client.parseroute.create(data=data)
Segmentation (Contact Filters)
Create expressions to dynamically filter your contacts (e.g., customers under 35) using contactfilter.
data = {
"Description": "Will send only to contacts under 35 years of age.",
"Expression": "(age<35)",
"Name": "Customers under 35",
}
result = client.contactfilter.create(data=data)
Retrieve Campaign Statistics
Retrieve performance counters using statcounters or location-based statistics via geostatistics.
from mailjet_rest import Client
import os
mailjet = Client(
auth=(
os.environ.get("MJ_APIKEY_PUBLIC", ""),
os.environ.get("MJ_APIKEY_PRIVATE", ""),
)
)
filters = {
"CounterSource": "APIKey",
"CounterTiming": "Message",
"CounterResolution": "Lifetime",
}
# Getting general statistics
result = mailjet.statcounters.get(filters=filters)
print(result.status_code)
print(result.json())
Content API
Requires version="v1". You can authenticate using Basic Auth or a Bearer Token.
The Content API (v1) allows managing templates, generating API tokens, and uploading images. The SDK handles the required /REST/ prefix for most resources automatically, while appropriately mapping data_images to /data/.
Generating a Token
# Tokens endpoint requires Basic Auth initially
client = Client(auth=(api_key, api_secret), version="v1")
data = {"Name": "My Access Token", "Permissions": ["read_template", "create_template"]}
result = client.token.create(data=data)
print(result.json())
Uploading an Image via Multipart Form-Data
To upload physical files, use the data_images resource and delete the default Content-Type header so requests can generate proper multipart boundaries. The request will be mapped to /v1/data/images.
import base64
# Base64 encoded image data (1x1 transparent PNG)
b64_string = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
image_bytes = base64.b64decode(b64_string)
# The Image upload requires a JSON metadata part (with a Status) and the physical file part
files_payload = {
"metadata": (None, '{"name": "logo.png", "Status": "open"}', "application/json"),
"file": ("logo.png", image_bytes, "image/png"),
}
# Deleting the default Content-Type header allows requests to generate multipart/form-data
result = client.data_images.create(headers={"Content-Type": None}, files=files_payload)
Locking a Template Content
Sub-actions are safely handled using slashes (e.g., template_contents_lock becomes template/<template_id>/contents/lock).
template_id = 1234567
# This routes to POST /v1/REST/template/1234567/contents/lock
result = client.template_contents_lock.create(id=template_id)
Deprecation Warnings
The SDK includes an active native Python deprecation system to protect your application from sudden API breaking changes.
If you attempt to use legacy arguments (like ensure_ascii or data_encoding), obsolete utility functions (parse_response), or ambiguous routing (v1 with /template), the SDK will not break your code.
It will successfully execute the request but will emit a non-breaking DeprecationWarning to help you gracefully migrate to modern standards.
Type Hinting
This SDK is fully type-hinted and compatible with static type checkers like mypy and pyright.
Because of the dynamic URL dispatch engine (__getattr__), IDEs may flag endpoints like client.contact.create as Any. If you enforce strict typing in your application, you may safely ignore these specific dynamically dispatched calls.
License
Contribute
Mailjet loves developers. You can be part of this project!
This wrapper is a great introduction to the open source world, check out the code!
Feel free to ask anything, and contribute:
- Fork the project.
- Create a new branch.
- Implement your feature or bug fix.
- Add documentation to it.
- Commit, push, open a pull request and voila.
If you have suggestions on how to improve the guides, please submit an issue in our Official API Documentation repo.
Contributors
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mailjet_rest-1.6.0rc1.tar.gz.
File metadata
- Download URL: mailjet_rest-1.6.0rc1.tar.gz
- Upload date:
- Size: 69.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd942e4d8bd4f6d502df00b79f4cc44ddba5173c0714e8d2dc6470881fa49679
|
|
| MD5 |
135cab2d282cf1c67282f9670686e974
|
|
| BLAKE2b-256 |
a7fca10885d8f48c4562660b5ed1a11b227c74da872df75e85d1a038871d6b98
|
File details
Details for the file mailjet_rest-1.6.0rc1-py3-none-any.whl.
File metadata
- Download URL: mailjet_rest-1.6.0rc1-py3-none-any.whl
- Upload date:
- Size: 53.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0177571bc7eb48eea10244a7b9eb59a5620a7220b19897626d225681d70defb0
|
|
| MD5 |
280e7d442fe7a5c5f6b0413a39ddca7e
|
|
| BLAKE2b-256 |
d6534f8d764844cc0130511bb3632878e44dc2610b12dab456b951f33152d157
|