# PHP SDK

The official PHP wrapper for the Mailjet API is installed with Composer as **`mailjet/mailjet-apiv3-php`** and developed at [mailjet/mailjet-apiv3-php](https://github.com/mailjet/mailjet-apiv3-php).

Current release: **2.0.0**

## Compatibility

The library requires **PHP 8.1 or higher**.

## Installation

```bash
composer require mailjet/mailjet-apiv3-php
```

The wrapper needs a [PSR-18](https://www.php-fig.org/psr/psr-18/) HTTP client and [PSR-17](https://www.php-fig.org/psr/psr-17/) factories. If your project does not already have one, Composer installs a compatible default (such as Guzzle) via [php-http/discovery](https://github.com/php-http/discovery).

To pin a specific client, require it explicitly:

```bash
# Guzzle
composer require guzzlehttp/guzzle

# Symfony HttpClient
composer require symfony/http-client nyholm/psr7
```

### Bring your own HTTP client

Since 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:

```php
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.

## Authentication

The Email API authenticates with your API key and secret:

```bash
export MJ_APIKEY_PUBLIC='your API key'
export MJ_APIKEY_PRIVATE='your API secret'
```

```php
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](#sms-api).

## Send your first email

```php
<?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());
```

## Client and call configuration

The constructor signature is:

```php
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` |


### API versioning

| 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`:

```php
$mj = new \Mailjet\Client(
    getenv('MJ_APIKEY_PUBLIC'),
    getenv('MJ_APIKEY_PRIVATE'),
    true,
    ['version' => 'v3.1']
);
```

### Base URL

The default base domain is `api.mailjet.com`. Override it with `url`:

```php
$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`.

### Network connectivity

If you hit intermittent `SSL_ERROR_SYSCALL` errors, force IPv4 on your HTTP client:

```php
$httpClient = new \GuzzleHttp\Client(['force_ip_resolve' => 'v4']);
$mj = new \Mailjet\Client(getenv('MJ_APIKEY_PUBLIC'), getenv('MJ_APIKEY_PRIVATE'), true, [], $httpClient);
```

### Disable the API call

Useful in tests, to build a request without reaching Mailjet — set the third argument to `false`:

```php
$mj = new \Mailjet\Client(getenv('MJ_APIKEY_PUBLIC'), getenv('MJ_APIKEY_PRIVATE'), false);
```

## Request examples

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`](https://github.com/mailjet/mailjet-apiv3-php/blob/master/src/Mailjet/Resources.php).

```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]);
```

### POST — create an object

`$params` accepts `body` (the object to create) and `id` (when acting on an existing resource).

```php
$body = [
    'Email' => 'passenger@example.com'
];

$response = $mj->post(Resources::$Contact, ['body' => $body]);
$response->success() && var_dump($response->getData());
```

### POST — endpoints with an action

```php
$body = [
    'ContactsLists' => [
        [
            'ListID' => $listID1,
            'Action' => 'addnoforce'
        ],
        [
            'ListID' => $listID2,
            'Action' => 'addforce'
        ]
    ]
];

$response = $mj->post(Resources::$ContactManagecontactslists, ['id' => $id, 'body' => $body]);
```

### GET — all objects

`$params` accepts `id` (a single object) and `filters` (query parameters).

```php
$response = $mj->get(Resources::$Contact);
$response->success() && var_dump($response->getData());
```

### GET — with filters

```php
$filters = [
    'IsExcludedFromCampaigns' => 'false'
];

$response = $mj->get(Resources::$Contact, ['filters' => $filters]);
```

### GET — with paging and sorting

```php
$filters = [
    'Limit' => 40,   // default 10, maximum 1000
    'Offset' => 20,
    'Sort' => 'ArrivedAt DESC',
    'Contact' => $contact->ID,
    'showSubject' => true
];

$response = $mj->get(Resources::$Message, ['filters' => $filters]);
```

### GET — a single object

```php
$response = $mj->get(Resources::$Contact, ['id' => $id]);
```

### PUT — update an object

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.

```php
$body = [
    'first_name' => 'John',
    'last_name' => 'Smith'
];

$response = $mj->put(Resources::$ContactData, ['id' => $id, 'body' => $body]);
```

### DELETE — remove an object

A successful `DELETE` returns `204 No Content` with no response body.

```php
$response = $mj->delete(Resources::$Template, ['id' => $id]);
```

Contact deletion (GDPR) uses the `v4` endpoint:

```php
$mj = new \Mailjet\Client(
    getenv('MJ_APIKEY_PUBLIC'),
    getenv('MJ_APIKEY_PRIVATE'),
    true,
    ['version' => 'v4']
);

$response = $mj->delete(Resources::$Contacts, ['ID' => $contactID]);
```

## Response object

`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 API

SMS endpoints authenticate with a bearer token generated in the [SMS section](https://app.mailjet.com/sms) of your account. Pass the token as the first argument and `NULL` as the second:

```php
$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());
```

## Further examples

### Emoji in a subject line

Use the string utility to get the UTF notation the API expects:

```php
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]);
```

### Retrieve campaigns with filters

Without query parameters, only campaigns sent since 00:00 UTC today are returned — set `period` to widen the window:

```php
$filters = [
    'period' => 'Year',
];

$response = $mj->get(Resources::$Campaign, ['filters' => $filters]);
```

### Retrieve ESP statistics

```php
$filters = [
    'CampaignID' => 123456,
];

$response = $mj->get(Resources::$Useragentstatistics, ['filters' => $filters]);
```

### Campaign overview

```php
$response = $mj->get(Resources::$Campaignoverview);
```

### Data transfer objects

DTOs give you typed objects instead of raw arrays:

```php
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);
}
```

### Create a template via the API

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.

```php
$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]);
}
```

## Contribute

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](https://github.com/mailjet/mailjet-apiv3-php). Documentation improvements belong in the [API documentation repo](https://github.com/mailjet/api-documentation).