Send emails with PHP
Send email from PHP with the official mailblastr/mailblastr Composer package — no runtime dependencies beyond the curl and json extensions.
The official `mailblastr/mailblastr` package is the fastest way to send email from PHP. It has no Composer runtime dependencies — just the built-in curl and json extensions — and is built around a single factory: Mailblastr::client($apiKey) returns a client that exposes one property per API resource.
The package also sets the required User-Agent header on every request for you (the API answers a request without one with a 403 validation_error), automatically retries 429 and 503, and throws a typed exception instead of handing back raw HTTP.
Prerequisites
- A verified domain for your
fromaddress. (guide) - An API key (
mb_...), stored in your environment asMAILBLASTR_API_KEY. (Authentication) - PHP 8.1 or newer with the
curlandjsonextensions enabled — the package declares"php": "^8.1",ext-curlandext-json. - Composer to install the package.
Install
composer require mailblastr/mailblastrThe current release is 5.0.0. The package autoloads under the Mailblastr\ PSR-4 namespace.
Create the client
Call the static Mailblastr::client() factory with your key. Read the key from the environment so the secret never lands in source control.
<?php
require __DIR__ . '/vendor/autoload.php';
use Mailblastr\Mailblastr;
$mailblastr = Mailblastr::client(getenv('MAILBLASTR_API_KEY'));A second array argument tunes the client. timeout (per-request seconds, default 30; 0 disables it) and maxRetries (default 2, 0 disables) configure the default curl transport; baseUrl overrides the API host, and transport swaps in any Mailblastr\Transport\TransportInterface implementation — useful for tests. Only 429 and 503 are ever retried, so a retry can never duplicate a send.
$mailblastr = Mailblastr::client(getenv('MAILBLASTR_API_KEY'), [
'timeout' => 30, // seconds per request (0 = no timeout)
'maxRetries' => 2, // automatic retries on 429/503 only (0 disables)
]);Send your first email
Every field of the send body is a plain array key — from is a reserved word in PHP, so the payload is an associative array rather than named arguments. to takes a list of up to 50 recipients.
<?php
require __DIR__ . '/vendor/autoload.php';
use Mailblastr\Mailblastr;
$mailblastr = Mailblastr::client(getenv('MAILBLASTR_API_KEY'));
$sent = $mailblastr->emails->send([
'from' => 'Acme <hello@yourdomain.com>',
'to' => ['delivered@mailblastr.dev'],
'subject' => 'Hello from PHP',
'html' => '<p>Your first email 🐘</p>',
]);
echo "Sent email {$sent['id']}\n";delivered@mailblastr.dev is the MailBlastr simulator: the send is accepted, recorded, and marked delivered without touching a real inbox. Swap it for your own address once the first call succeeds — and never use @example.com, which the API rejects with 422.Handling the response
Every method returns the decoded JSON response as an associative array, so a successful send gives you $sent['id']. Any non-2xx answer throws Mailblastr\Exceptions\MailblastrException, which carries the { statusCode, name, message } envelope. Branch on getName() together with getStatusCode(), never on the message — messages are scrubbed server-side and are not a stable contract.
use Mailblastr\Exceptions\MailblastrException;
try {
$sent = $mailblastr->emails->send($payload);
echo "Sent email {$sent['id']}\n";
} catch (MailblastrException $e) {
echo $e->getStatusCode(); // 422
echo $e->getName(); // 'validation_error'
echo $e->getMessage(); // human text — do not match on it
// WHICH quota ran out, and what would clear it (null on other errors).
if ($limit = $e->getLimit()) {
echo "{$limit['kind']}: {$limit['used']}/{$limit['limit']} used";
}
// Reputation gates: whether waiting helps, and until when.
if ($rep = $e->getReputation()) {
echo $rep['retryable'] ? "retry at {$rep['retry_at']}" : 'not retryable';
}
print_r($e->getBody()); // the whole parsed error body
}A transport-level failure throws the same exception with getStatusCode() === 0 and a getName() of network_error, so one catch clause covers both.
Retry safely with an idempotency key
Pass ['idempotencyKey' => '...'] as the second argument so replaying a request returns the original response instead of sending twice. Only emails->send() and batch->send() honour it — see Idempotency keys.
$mailblastr->emails->send($payload, ['idempotencyKey' => 'order-123']);
// Up to 100 emails in one request (batch items reject attachments and scheduled_at).
$mailblastr->batch->send([
['from' => 'hello@yourdomain.com', 'to' => ['delivered@mailblastr.dev'], 'subject' => 'Hi A', 'html' => '<p>A</p>'],
['from' => 'hello@yourdomain.com', 'to' => ['delivered@mailblastr.dev'], 'subject' => 'Hi B', 'html' => '<p>B</p>'],
], ['idempotencyKey' => 'orders-2026-08-19']);If a batch fails part way through, the exception names the emails that already went out — read $e->getSent() and $e->getSentCount() and do not resend those.
Schedule instead of sending now
Add scheduled_at (ISO 8601, at most 30 days ahead) to hand the send to the scheduler. A scheduled send goes through the real delivery path, so it cannot target the simulator address — use one of your own recipients.
$scheduled = $mailblastr->emails->send([
'from' => 'Acme <hello@yourdomain.com>',
'to' => ['you@yourdomain.com'],
'subject' => 'Tomorrow morning',
'html' => '<p>Queued ahead of time.</p>',
'scheduled_at' => '2026-09-01T09:00:00Z',
]);
$mailblastr->emails->update($scheduled['id'], ['scheduled_at' => '2026-09-01T14:00:00Z']); // reschedule
$mailblastr->emails->cancel($scheduled['id']); // or call it offmb_ API key server-side — load it from an environment variable or secrets manager, never commit it, and never echo it into a Blade view or any browser response. Anyone holding the key can send email as you.Next steps
- See the full Send Email API reference for every body field, including attachments.
- Using Laravel? See Send emails with PHP & Laravel.
- Explore the SDKs reference for the other resources the client exposes —
domains,contacts,campaigns,automations,webhooks,polls, and more (plus inbound mail viaemails->receiving). - Want no dependency at all? PHP without the SDK does the same send with the raw cURL extension.