Skip to main content

Mailjet V3 API wrapper

Project description

alt text

Official Mailjet Python Wrapper

PyPI Version GitHub Release Python Versions License PyPI Downloads Build Status GitHub Stars GitHub Issues GitHub PRs

Table of contents

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 API
  • v3.1 - Email Send API v3.1, which is the latest version of our Send API
  • v1 - 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

MIT

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mailjet_rest-1.6.0rc1.tar.gz (69.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mailjet_rest-1.6.0rc1-py3-none-any.whl (53.3 kB view details)

Uploaded Python 3

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

Hashes for mailjet_rest-1.6.0rc1.tar.gz
Algorithm Hash digest
SHA256 bd942e4d8bd4f6d502df00b79f4cc44ddba5173c0714e8d2dc6470881fa49679
MD5 135cab2d282cf1c67282f9670686e974
BLAKE2b-256 a7fca10885d8f48c4562660b5ed1a11b227c74da872df75e85d1a038871d6b98

See more details on using hashes here.

File details

Details for the file mailjet_rest-1.6.0rc1-py3-none-any.whl.

File metadata

File hashes

Hashes for mailjet_rest-1.6.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 0177571bc7eb48eea10244a7b9eb59a5620a7220b19897626d225681d70defb0
MD5 280e7d442fe7a5c5f6b0413a39ddca7e
BLAKE2b-256 d6534f8d764844cc0130511bb3632878e44dc2610b12dab456b951f33152d157

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page