mailgun / mailgun-php
The Mailgun SDK provides methods for all API functions.
Requires
- php: ^7.4 || ^8.0
- php-http/client-common: ^2.2.1
- php-http/discovery: ^1.19
- php-http/multipart-stream-builder: ^1.1.2
- psr/http-client: ^1.0
- webmozart/assert: ^1.9.1
Requires (Dev)
- ergebnis/composer-normalize: ^2.43
- nyholm/nsa: ^1.2.1
- nyholm/psr7: ^1.3.1
- phpcompatibility/php-compatibility: ^9.3
- phpunit/phpunit: ^9.3
- squizlabs/php_codesniffer: ^3.7
- symfony/http-client: ^5.4 || ^6.3
- vimeo/psalm: ^4.0 || ^5.25
Suggests
- nyholm/psr7: PSR-7 message implementation
- symfony/http-client: HTTP client
- v4.5.0
- v4.4.0
- v4.3.7
- v4.3.6
- v4.3.5
- v4.3.4
- v4.3.3
- v4.3.2
- v4.3.1
- v4.3.0
- v4.2.0
- v4.1.0
- v4.0.1
- v4.0
- v3.6.3
- v3.6.2
- v3.6.1
- v3.6.0
- v3.5.9
- v3.5.8
- v3.5.7
- v3.5.6
- v3.5.5
- v3.5.4
- v3.5.3
- v3.5.2
- v3.5.1
- 3.5.0
- 3.4.1
- 3.4.0
- 3.3.0
- 3.2.0
- 3.1.0
- dev-master / 3.0.x-dev
- 3.0.0
- 3.0.0-beta2
- 3.0.0-beta1
- 2.x-dev
- 2.8.1
- 2.8.0
- 2.7.0
- 2.6.0
- 2.5.0
- 2.4.1
- v2.4.0
- 2.3.4
- v2.3.3
- v2.3.2
- v2.3.1
- v2.3.0
- v2.2.0
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0
- v1.8
- v1.7.2
- v1.7.1
- v1.7
- 1.6
- 1.5
- 1.4
- 1.3
- 1.2
- 1.1
- 1.0
- 0.7
- dev-ip-classes-imporvements
- dev-DE-1760-mailgun-sdk-dynamic-ip-pools
- dev-DE-1744-mailgun-sdk-query-account-metrics-dimension-support
- dev-DE-1745-mailgun-sdk-mailgun-webhooks-bc-in-v-4-3-7
- dev-prepare-release
- dev-readme-update
- dev-prepare-for-3.4.0
This package is auto-updated.
Last update: 2026-04-29 18:20:00 UTC
README
The official Mailgun PHP SDK — a clean, PSR-18 HTTP client wrapper around the Mailgun API.
Table of Contents
- Requirements
- Installation
- Quick Start
- Sending Email
- IP Management
- Dynamic IP Pools
- Analytics
- Subaccounts
- Response Handling
- Debugging
- Framework Integration
- Contributing
Requirements
- PHP 7.4 or higher
- A PSR-18 HTTP client (e.g.
symfony/http-client,guzzlehttp/guzzle) - A PSR-7 / PSR-17 implementation (e.g.
nyholm/psr7)
The SDK is not coupled to any specific HTTP library — bring your own PSR-18 client.
Installation
composer require mailgun/mailgun-php symfony/http-client nyholm/psr7
EU region? Use
https://api.eu.mailgun.netas your endpoint (see below).
Quick Start
require 'vendor/autoload.php'; use Mailgun\Mailgun; // US servers (default) $mg = Mailgun::create('your-api-key'); // EU servers $mg = Mailgun::create('your-api-key', 'https://api.eu.mailgun.net');
Note: The
$domainyou pass to any API call must match a domain configured in app.mailgun.com.
Sending Email
Simple message
$mg->messages()->send('example.com', [ 'from' => 'Alice <alice@example.com>', 'to' => 'bob@example.com', 'subject' => 'Hello from Mailgun!', 'text' => 'This is a plain-text body.', 'html' => '<p>This is an <strong>HTML</strong> body.</p>', ]);
With attachments and tracking options
$mg->messages()->send('example.com', [ 'from' => 'alice@example.com', 'to' => ['bob@example.com', 'carol@example.com'], 'subject' => 'Monthly report', 'text' => 'Please find the report attached.', 'attachment' => [['filePath' => '/tmp/report.pdf', 'filename' => 'report.pdf']], 'o:tracking' => 'yes', 'o:tag' => ['monthly', 'report'], ]);
Scheduled delivery
$mg->messages()->send('example.com', [ 'from' => 'alice@example.com', 'to' => 'bob@example.com', 'subject' => 'See you tomorrow', 'text' => 'Scheduled for tomorrow morning.', 'o:deliverytime' => 'tomorrow 9am UTC', ]);
IP Management
List all account IPs
$response = $mg->ips()->index(); foreach ($response->getItems() as $ip) { echo $ip . PHP_EOL; } // Dedicated only $dedicated = $mg->ips()->index(dedicated: true); // With full details (pool assignments, subaccount ownership, timestamps) $detailed = $mg->ips()->listIpsDetailed([ 'pool_id' => 'my-pool-id', // filter by pool ('any' or 'none' also accepted) 'subaccount_id'=> 'sub-123', 'limit' => 25, ]); foreach ($detailed->getItems() as $ip) { echo "{$ip['address']} — pools: " . implode(', ', $ip['pool_ids']) . PHP_EOL; }
Inspect a single IP
$ip = $mg->ips()->show('1.2.3.4'); echo $ip->getIp(); // "1.2.3.4" echo $ip->getRdns(); // reverse DNS var_dump($ip->getDedicated()); // bool
Assign / remove an IP on a specific domain
// Add IP to a domain $mg->ips()->assign('example.com', '1.2.3.4'); // Remove IP from a domain $mg->ips()->unassign('example.com', '1.2.3.4'); // List IPs currently assigned to a domain $response = $mg->ips()->domainIndex('example.com'); print_r($response->getItems());
Bulk IP operations across all domains
// Assign an IP to every domain in the account (async) $ref = $mg->ips()->assignIpToAllDomains('1.2.3.4'); echo $ref->getReferenceId(); // track the async operation // Remove an IP from all domains, replacing it with another $ref = $mg->ips()->removeIpFromAllDomains('1.2.3.4', alternative: '5.6.7.8'); echo $ref->getMessage();
Find all domains using a specific IP
$response = $mg->ips()->domainsByIp('1.2.3.4', limit: 20, search: 'example'); foreach ($response->getItems() as $domain) { echo $domain . PHP_EOL; }
Dedicated IP band
// Move an account IP into a dedicated IP band $mg->ips()->placeAccountIpToBand('1.2.3.4');
Request a new dedicated IP
// Check how many dedicated IPs your plan allows $available = $mg->ips()->numberOfIps(); // Provision a new dedicated IP $mg->ips()->addDedicatedIp();
Dynamic IP Pools (DIPP)
Dynamic IP Pools let you group dedicated IPs and link them to domains, so sending traffic is spread across all IPs in the pool automatically.
List and inspect pools
// All pools in the account $response = $mg->ips()->listIpPools(); foreach ($response->getIpPools() as $pool) { echo "{$pool['pool_id']} — {$pool['name']}" . PHP_EOL; echo " IPs: " . implode(', ', $pool['ips']) . PHP_EOL; echo " Linked to domains: " . ($pool['is_linked'] ? 'yes' : 'no') . PHP_EOL; } // Single pool details $pool = $mg->ips()->loadDIPPInformation('my-pool-id'); echo $pool->getPoolId(); // "my-pool-id" echo $pool->getName(); // "Primary sending pool" echo $pool->getDescription(); // "Main US sending pool" print_r($pool->getIps()); // ["1.2.3.4", "5.6.7.8"] var_dump($pool->isLinked()); // bool — whether domains are attached
Create and configure a pool
// Create a new pool $mg->ips()->createIpPool('Primary Pool', 'Main US outbound pool'); // Modify pool metadata, add/remove IPs, link/unlink domains — all in one call $mg->ips()->updateIpPool('my-pool-id', [ 'name' => 'Primary Pool v2', 'add_ip' => '9.10.11.12', 'remove_ip' => '1.2.3.4', 'link_domain' => 'example.com', 'unlink_domain' => 'old.example.com', ]);
Manage IPs inside a pool
// Add a single IP to a pool $mg->ips()->addIpToPool('my-pool-id', '9.10.11.12'); // Add multiple IPs at once $mg->ips()->addIpsToPool('my-pool-id', ['9.10.11.12', '13.14.15.16']); // Remove an IP from a pool $mg->ips()->removeIpFromPool('my-pool-id', '1.2.3.4');
When a pool is linked to domains, adding or removing IPs propagates to all linked domains asynchronously after the API responds.
List domains linked to a pool
$response = $mg->ips()->getIpPoolDomains('my-pool-id', limit: 20); foreach ($response->getDomains() as $domain) { echo $domain['name'] . PHP_EOL; } // Paginate using the cursor from the previous response if ($response->getNextPage()) { $next = $mg->ips()->getIpPoolDomains('my-pool-id', page: $response->getNextPage()); }
Delete a pool
// Delete without replacement (pool must not be linked to any domains) $mg->ips()->deleteDIPP('my-pool-id'); // Replace linked domains with a specific IP before deleting $mg->ips()->deleteDIPP('my-pool-id', replacementIp: '1.2.3.4'); // Replace linked domains with another pool before deleting $mg->ips()->deleteDIPP('my-pool-id', replacementPoolId: 'backup-pool-id');
Delegate a pool to a subaccount
// Grant a subaccount access to a pool $mg->ips()->delegateIpPool('my-pool-id', 'sub-account-id'); // Revoke subaccount access $mg->ips()->revokeDelegatedIpPool('my-pool-id', 'sub-account-id');
Analytics
$result = $mg->metrics()->loadMetrics([ 'start' => 'Sun, 22 Dec 2024 00:00:00 +0000', 'end' => 'Sun, 29 Dec 2024 00:00:00 +0000', 'resolution' => 'day', 'dimensions' => ['time'], 'metrics' => ['accepted_count', 'delivered_count', 'clicked_rate', 'opened_rate'], 'include_aggregates' => true, 'include_subaccounts' => true, ]); foreach ($result->getItems() as $item) { echo $item['dimensions']['time'] . ': ' . $item['metrics']['delivered_count'] . ' delivered' . PHP_EOL; }
Subaccounts
// Create a subaccount $mg->subaccounts()->create('Marketing Team'); // List all subaccounts $items = $mg->subaccounts()->index(); print_r($items->getItems()); // Enable / disable $mg->subaccounts()->enable($subAccountId); $mg->subaccounts()->disable($subAccountId);
Make API calls on behalf of a subaccount
// Pass the subaccount ID as the third argument to Mailgun::create() $mg = Mailgun::create('your-api-key', 'https://api.mailgun.net', $subAccountId); // All subsequent calls are scoped to that subaccount $mg->messages()->send('example.com', [...]);
Response Handling
All API methods return typed model objects with IDE-friendly getters by default.
$domain = $mg->domains()->show('example.com'); foreach ($domain->getInboundDNSRecords() as $record) { echo $record->getType() . ': ' . $record->getValue() . PHP_EOL; }
Array responses
Prefer raw arrays? Inject ArrayHydrator:
use Mailgun\Hydrator\ArrayHydrator; use Mailgun\HttpClient\HttpClientConfigurator; $configurator = new HttpClientConfigurator(); $configurator->setApiKey('your-api-key'); $mg = new Mailgun($configurator, new ArrayHydrator()); $data = $mg->domains()->show('example.com'); // $data is now a plain associative array
Raw PSR-7 response
Need the raw response? Use NoopHydrator — note: no exceptions are thrown on non-200 responses when using this hydrator.
use Mailgun\Hydrator\NoopHydrator; $mg = new Mailgun($configurator, new NoopHydrator()); $response = $mg->messages()->send('example.com', [...]); // $response is a Psr\Http\Message\ResponseInterface echo $response->getStatusCode();
Debugging
Route traffic through Mailgun's Postbin to inspect what the SDK sends:
use Mailgun\HttpClient\HttpClientConfigurator; use Mailgun\Hydrator\NoopHydrator; $configurator = new HttpClientConfigurator(); $configurator->setEndpoint('http://bin.mailgun.net/aecf68de'); // replace with your bin ID $configurator->setApiKey('your-api-key'); $configurator->setDebug(true); $mg = new Mailgun($configurator, new NoopHydrator()); $mg->messages()->send('example.com', [ 'from' => 'alice@example.com', 'to' => 'bob@example.com', 'subject' => 'Debug test', 'text' => 'Checking what hits the wire.', ]);
Custom HTTP requests
$client = $mg->httpClient(); $client->httpGet('/v3/domains', ['limit' => 5]); $client->httpPost('/v3/some/path', ['key' => 'value']); $client->httpPut('/v3/some/path', ['key' => 'value']); $client->httpDelete('/v3/some/path');
Framework Integration
| Framework | Package |
|---|---|
| Symfony | tehplague/swiftmailer-mailgun-bundle |
| Yii2 | katanyoo/yii2-mailgun-mailer |
| CakePHP | narendravaghela/cakephp-mailgun |
| Drupal | drupal/mailgun |
| Laravel | Built-in — see Laravel Mail docs |
Contributing
This is an open-source project under the MIT license, maintained by Mailgun and the community.
Running the tests
git clone git@github.com:mailgun/mailgun-php.git cd mailgun-php composer install composer test
Ways to help
- Test the
dev-masterbranch and open issues for anything broken - Review open pull requests
- Add tests for untested endpoints
- Improve documentation and examples
Support
- Documentation: documentation.mailgun.com
- Issues: GitHub Issues
- Account support: app.mailgun.com/support
- More examples: doc/examples.md