mailengin / mailengin-php
Official PHP SDK for the MailEngin Email API
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
The official PHP SDK for sending transactional email through MailEngin. It provides typed request and response objects, Guzzle integration, configurable timeouts, and structured exceptions.
Important
This package is for server-side applications only. Never expose a MailEngin API key in JavaScript, browser, mobile, or other client-distributed code.
Requirements
- PHP 8.2 or newer
- Composer 2
- A MailEngin API key and verified sending domain
Installation
composer require mailengin/mailengin-php
Before You Send
- Verify a sending domain.
- Create an API key and save the full secret.
- Create and publish a Developer Template.
- Copy the template API name, such as
welcome-email.
Store the API key in your server's secret manager or environment:
MAILENGIN_API_KEY=re_your_full_secret_key
MailEngin displays the full key only once. A masked key cannot authenticate requests.
Quick Start
<?php declare(strict_types=1); require __DIR__ . '/vendor/autoload.php'; use MailEngin\MailEnginClient; use MailEngin\Model\SendEmailRequest; $client = new MailEnginClient($_ENV['MAILENGIN_API_KEY']); $email = $client->emails()->send(new SendEmailRequest( to: 'user@example.com', fromEmail: 'hello@yourdomain.com', templateName: 'welcome-email', variables: ['first_name' => 'Asha'], )); echo $email->id;
The published template supplies the subject and HTML. Values in variables replace matching template variables such as {{first_name}}.
Send One Email
use MailEngin\Model\SendEmailRequest; $email = $client->emails()->send(new SendEmailRequest( to: 'customer@example.com', fromEmail: 'hello@yourdomain.com', templateName: 'account-verification', variables: [ 'first_name' => 'Asha', 'verification_url' => 'https://yourapp.com/verify/token', ], replyToMailEngin: true, )); printf("Queued email %s at %s\n", $email->id, $email->createdAt);
Send request fields
| Constructor argument | Type | Required | Description |
|---|---|---|---|
to |
string |
Yes | Recipient email address. |
templateName |
?string |
Recommended | Published template API name or exact display name. |
templateId |
?string |
No | Legacy template identifier. Prefer templateName. |
variables |
?array |
No | Values used to render template variables. |
subject |
?string |
Raw HTML only | Template subject override, or required subject for raw HTML. |
fromEmail |
?string |
Recommended | Sender on a verified domain authorized for the API key. |
html |
?string |
Advanced | Raw HTML used when no template is supplied. |
replyToMailEngin |
?bool |
No | Route recipient replies into the MailEngin inbox. |
Exactly one content source is required: templateName, templateId, or html. Raw HTML sends also require subject.
Send Personalized Bulk Email
Bulk requests support up to 1,000 recipients. Request-level variables apply to every recipient; recipient variables take precedence.
use MailEngin\Model\BulkRecipient; use MailEngin\Model\SendBulkEmailRequest; $job = $client->emails()->sendBulk(new SendBulkEmailRequest( to: [ new BulkRecipient( email: 'asha@example.com', variables: ['first_name' => 'Asha'], ), new BulkRecipient( email: 'ben@example.com', variables: ['first_name' => 'Ben'], ), ], fromEmail: 'hello@yourdomain.com', templateName: 'product-update', variables: ['product_name' => 'MailEngin'], )); printf("Queued %d recipients in job %s\n", $job->queuedCount, $job->jobId);
For the same content without recipient-specific variables, use strings:
$job = $client->emails()->sendBulk(new SendBulkEmailRequest( to: ['a@example.com', 'b@example.com'], templateName: 'maintenance-notice', ));
A successful bulk response confirms that recipients were queued. It is not a guarantee that every message was delivered.
Send Raw HTML
Published templates are recommended for reusable product email. For a one-off message, provide both subject and html:
$email = $client->emails()->send(new SendEmailRequest( to: 'user@example.com', fromEmail: 'reports@yourdomain.com', subject: 'Your report is ready', html: '<h1>Report ready</h1><p>You can download it now.</p>', ));
Sender Selection
MailEngin resolves the sender in this order:
fromEmailsupplied in the request.- Sender saved in the published Developer Template.
noreply@<authorized-domain>fallback.
The sender domain must be verified and authorized for the API key. Set the sender in the template or request for predictable production sends.
Error Handling
API, timeout, malformed-response, and network failures throw MailEnginError:
use MailEngin\Exception\MailEnginError; use MailEngin\Model\SendEmailRequest; try { $client->emails()->send(new SendEmailRequest( to: 'user@example.com', templateName: 'welcome-email', )); } catch (MailEnginError $error) { echo $error->getMessage(); var_dump($error->status); // HTTP status, when available var_dump($error->errorCode); // Machine-readable error code var_dump($error->requestId); // Include when contacting support var_dump($error->retryAfter); // Seconds supplied with HTTP 429 var_dump($error->body); // Parsed JSON or response text var_dump($error->isRetryable()); }
isRetryable() returns true for network errors, timeouts, HTTP 408, HTTP 429, and 5xx responses. The SDK never retries sends automatically because a retry could create a duplicate email until idempotency keys are supported.
Invalid local input, such as an empty recipient or raw HTML without a subject, throws InvalidArgumentException before an API request is made.
Configuration
use MailEngin\MailEnginClient; $client = new MailEnginClient( apiKey: $_ENV['MAILENGIN_API_KEY'], baseUrl: 'https://api.mailengin.app', timeout: 15.0, );
| Constructor argument | Default | Description |
|---|---|---|
apiKey |
None | Full server-side MailEngin API key. |
baseUrl |
https://api.mailengin.app |
Override for local, test, or dedicated environments. |
timeout |
30.0 |
Request timeout in seconds. |
httpClient |
New Guzzle client | Injectable PSR-compatible ClientInterface implementation. |
Framework Integration
Create one client in your dependency-injection container and reuse it. For example, a Laravel service provider can register a singleton:
use Illuminate\Contracts\Foundation\Application; use MailEngin\MailEnginClient; $this->app->singleton(MailEnginClient::class, function (Application $app): MailEnginClient { return new MailEnginClient((string) config('services.mailengin.api_key')); });
Configuration belongs in config/services.php, backed by MAILENGIN_API_KEY; do not commit the secret to source control.
Testing With an Injected Client
Use Guzzle's mock handler to test your integration without a real API key or network request:
use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Psr7\Response; use MailEngin\MailEnginClient; $mock = new MockHandler([ new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'id' => 'email_123', 'from' => 'hello@example.com', 'to' => 'user@example.com', 'template_name' => 'welcome-email', 'created_at' => '2026-08-31T12:00:00Z', ], JSON_THROW_ON_ERROR)), ]); $httpClient = new Client(['handler' => HandlerStack::create($mock)]); $client = new MailEnginClient('test_key', httpClient: $httpClient);
Development
composer install
composer validate --strict
composer analyse
composer test
composer archive --format=zip
See CONTRIBUTING.md for contribution rules and PUBLISHING.md for maintainer release instructions.
Resources
License
Released under the MIT License. Copyright 2026 MailEngin.