The official PHP wrapper for the Mailjet API is installed with Composer as mailjet/mailjet-apiv3-php and developed at mailjet/mailjet-apiv3-php.
Current release: 2.0.0
The library requires PHP 8.1 or higher.
composer require mailjet/mailjet-apiv3-phpThe wrapper needs a PSR-18 HTTP client and PSR-17 factories. If your project does not already have one, Composer installs a compatible default (such as Guzzle) via php-http/discovery.
To pin a specific client, require it explicitly:
# Guzzle
composer require guzzlehttp/guzzle
# Symfony HttpClient
composer require symfony/http-client nyholm/psr7Since 2.0 the transport layer is fully delegated to the injected PSR-18 client, which is what makes timeouts, proxies and retries your own to configure:
use \Mailjet\Client;
// auto-discovery (default) — uses whatever PSR-18 client is installed
$mj = new Client($apikey, $apisecret);
// inject a specific HTTP client
$httpClient = new \GuzzleHttp\Client(['timeout' => 30, 'proxy' => 'tcp://localhost:8080']);
$mj = new Client($apikey, $apisecret, true, [], $httpClient);
// inject a Symfony HttpClient
$httpClient = new \Symfony\Component\HttpClient\Psr18Client();
$mj = new Client($apikey, $apisecret, true, [], $httpClient);PSR-17 request and stream factories can be injected as the sixth and seventh constructor arguments.
The Email API authenticates with your API key and secret:
export MJ_APIKEY_PUBLIC='your API key'
export MJ_APIKEY_PRIVATE='your API secret'use \Mailjet\Resources;
$apikey = getenv('MJ_APIKEY_PUBLIC');
$apisecret = getenv('MJ_APIKEY_PRIVATE');
$mj = new \Mailjet\Client($apikey, $apisecret);The SMS API authenticates with a bearer token instead — see SMS API.
<?php
require 'vendor/autoload.php';
use \Mailjet\Resources;
$mj = new \Mailjet\Client(
getenv('MJ_APIKEY_PUBLIC'),
getenv('MJ_APIKEY_PRIVATE'),
true,
['version' => 'v3.1']
);
$body = [
'Messages' => [
[
'From' => [
'Email' => 'pilot@example.com',
'Name' => 'Mailjet Pilot'
],
'To' => [
[
'Email' => 'passenger@example.com',
'Name' => 'Passenger 1'
]
],
'Subject' => 'My first Mailjet Email!',
'TextPart' => 'Greetings from Mailjet!',
'HTMLPart' => '<h3>Dear passenger 1, welcome to Mailjet!</h3>'
]
]
];
$response = $mj->post(Resources::$Email, ['body' => $body]);
$response->success() && var_dump($response->getData());The constructor signature is:
new \Mailjet\Client(
$MJ_APIKEY_PUBLIC,
$MJ_APIKEY_PRIVATE,
$CALL,
$OPTIONS,
$HTTP_CLIENT,
$REQUEST_FACTORY,
$STREAM_FACTORY
);| Argument | Meaning |
|---|---|
$MJ_APIKEY_PUBLIC | public API key |
$MJ_APIKEY_PRIVATE | private API key |
$CALL | boolean; true performs the actual API call |
$OPTIONS | associative array of connection options |
$HTTP_CLIENT | optional PSR-18 ClientInterface — auto-discovered if null |
$REQUEST_FACTORY | optional PSR-17 RequestFactoryInterface |
$STREAM_FACTORY | optional PSR-17 StreamFactoryInterface |
| Version | Scope |
|---|---|
v3 | Email API |
v3.1 | Send API v3.1 (latest send version) |
v4 | SMS API, contact delete API |
Most Email API endpoints sit under v3, which is the default. For anything else, set version:
$mj = new \Mailjet\Client(
getenv('MJ_APIKEY_PUBLIC'),
getenv('MJ_APIKEY_PRIVATE'),
true,
['version' => 'v3.1']
);The default base domain is api.mailjet.com. Override it with url:
$mj = new \Mailjet\Client(
getenv('MJ_APIKEY_PUBLIC'),
getenv('MJ_APIKEY_PRIVATE'),
true,
['url' => 'api.us.mailjet.com']
);Accounts on Mailjet's US architecture must set api.us.mailjet.com.
If you hit intermittent SSL_ERROR_SYSCALL errors, force IPv4 on your HTTP client:
$httpClient = new \GuzzleHttp\Client(['force_ip_resolve' => 'v4']);
$mj = new \Mailjet\Client(getenv('MJ_APIKEY_PUBLIC'), getenv('MJ_APIKEY_PRIVATE'), true, [], $httpClient);Useful in tests, to build a request without reaching Mailjet — set the third argument to false:
$mj = new \Mailjet\Client(getenv('MJ_APIKEY_PUBLIC'), getenv('MJ_APIKEY_PRIVATE'), false);All resources are constants on the Resources class, which maps PHP resource names to their API reference names. The full list is in /src/Mailjet/Resources.php.
$response = $mj->delete(Resources::$Template, ['id' => $id]);
$response = $mj->put(Resources::$ContactData, ['id' => $id, 'body' => $body]);
$response = $mj->post(Resources::$ContactManagecontactslists, ['id' => $id, 'body' => $body]);$params accepts body (the object to create) and id (when acting on an existing resource).
$body = [
'Email' => 'passenger@example.com'
];
$response = $mj->post(Resources::$Contact, ['body' => $body]);
$response->success() && var_dump($response->getData());$body = [
'ContactsLists' => [
[
'ListID' => $listID1,
'Action' => 'addnoforce'
],
[
'ListID' => $listID2,
'Action' => 'addforce'
]
]
];
$response = $mj->post(Resources::$ContactManagecontactslists, ['id' => $id, 'body' => $body]);$params accepts id (a single object) and filters (query parameters).
$response = $mj->get(Resources::$Contact);
$response->success() && var_dump($response->getData());$filters = [
'IsExcludedFromCampaigns' => 'false'
];
$response = $mj->get(Resources::$Contact, ['filters' => $filters]);$filters = [
'Limit' => 40, // default 10, maximum 1000
'Offset' => 20,
'Sort' => 'ArrivedAt DESC',
'Contact' => $contact->ID,
'showSubject' => true
];
$response = $mj->get(Resources::$Message, ['filters' => $filters]);$response = $mj->get(Resources::$Contact, ['id' => $id]);A PUT in the Mailjet API behaves like a PATCH: only the properties you send are updated, everything else is left untouched, and non-mandatory properties can be omitted.
$body = [
'first_name' => 'John',
'last_name' => 'Smith'
];
$response = $mj->put(Resources::$ContactData, ['id' => $id, 'body' => $body]);A successful DELETE returns 204 No Content with no response body.
$response = $mj->delete(Resources::$Template, ['id' => $id]);Contact deletion (GDPR) uses the v4 endpoint:
$mj = new \Mailjet\Client(
getenv('MJ_APIKEY_PUBLIC'),
getenv('MJ_APIKEY_PRIVATE'),
true,
['version' => 'v4']
);
$response = $mj->delete(Resources::$Contacts, ['ID' => $contactID]);get, post, put and delete return a Response object:
| Method | Returns |
|---|---|
success() | whether the call succeeded |
getStatus() | HTTP status code (200, 400, …) |
getData() | the Data property of the JSON payload as a PHP associative array, or the full payload if there is none |
getCount() | number of objects returned |
getReasonPhrase() | HTTP reason phrase ("OK", "Bad Request", …) |
SMS endpoints authenticate with a bearer token generated in the SMS section of your account. Pass the token as the first argument and NULL as the second:
$mj = new \Mailjet\Client(
getenv('MJ_APITOKEN'),
NULL,
true,
['url' => 'api.mailjet.com', 'version' => 'v4', 'call' => false]
);
$body = [
'Text' => 'Have a nice SMS flight with Mailjet!',
'To' => '+33600000000',
'From' => 'MJPilot',
];
$response = $mj->post(Resources::$SmsSend, ['body' => $body]);
$response->success() && var_dump($response->getData());Use the string utility to get the UTF notation the API expects:
use \Mailjet\Resources;
$subject = \Mailjet\Utility\StringUtility::utfStringNotation('This is a subject with emoji 🤑');
$body = [
'Locale' => 'en_US',
'Sender' => 'sender@example.com',
'SenderEmail' => 'sender@example.com',
'Subject' => $subject,
'TemplateID' => 12345,
'ContactsListID' => 12345,
'Title' => 'Emoji Test ' . time(),
];
$response = $mj->post(Resources::$Campaigndraft, ['body' => $body]);Without query parameters, only campaigns sent since 00:00 UTC today are returned — set period to widen the window:
$filters = [
'period' => 'Year',
];
$response = $mj->get(Resources::$Campaign, ['filters' => $filters]);$filters = [
'CampaignID' => 123456,
];
$response = $mj->get(Resources::$Useragentstatistics, ['filters' => $filters]);$response = $mj->get(Resources::$Campaignoverview);DTOs give you typed objects instead of raw arrays:
use Mailjet\Model\MailjetCampaignDataDTO;
use \Mailjet\Resources;
$filters = [
'period' => 'Month',
'Limit' => 10,
];
$response = $mj->get(
Resources::$Campaigndraft + ['model' => MailjetCampaignDataDTO::class],
['filters' => $filters]
);
foreach ($response->getData() as $campaign) {
/** @var MailjetCampaignDataDTO $campaign */
print_r($campaign);
}Creating a template takes two calls: one for the metadata, one for the content. The content endpoint does not accept MJML — send HTML and text parts.
$body = [
'Author' => 'John Doe',
'Copyright' => 'Mailjet',
'Description' => 'Used to send out promo codes.',
'EditMode' => 2,
'IsStarred' => false,
'IsTextPartGenerationEnabled' => true,
'Locale' => 'en_US',
'Name' => 'promo-codes',
'OwnerType' => 'user',
'Purposes' => ['marketing'],
];
$response = $mj->post(Resources::$Template, ['body' => $body]);
$id = $response->getData()[0]['ID'];
if ($id) {
$content = [
'Headers' => '',
'Html-part' => '<h3>Dear passenger, welcome to Mailjet!</h3>',
'MJMLContent' => '',
'Text-part' => 'Dear passenger, welcome to Mailjet!'
];
$mj->post(Resources::$TemplateDetailcontent, ['id' => $id, 'body' => $content]);
}The wrapper is open source. Fork the repository, branch, implement your fix or feature, document it, and open a pull request at mailjet/mailjet-apiv3-php. Documentation improvements belong in the API documentation repo.