wazobiamail / wazobiamail-php
Official PHP SDK for WazobiaMail transactional email via API. Start free with 3,000 emails/month. A Resend alternative that also supports SMTP, with domains, hosted mailboxes, and deliverability tools.
Requires
- php: ^8.1 || ^8.2 || ^8.3 || ^8.4 || ^8.5
- ext-json: *
- guzzlehttp/guzzle: ^7.5
Requires (Dev)
- phpunit/phpunit: ^10.0 || ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Start free with 3,000 emails/month. WazobiaMail is a Resend alternative for transactional email through an API or SMTP, with sending domains, hosted mailboxes and deliverability tools.
The official PHP SDK for WazobiaMailβbusiness email infrastructure, transactional sending, domain setup with automated DNS generation, hosted mailboxes, and enterprise programmatic inbox automation.
π Official Platform Documentation: Visit docs.wazobiamail.com for exhaustive API references, architecture guides, DNS setup walkthroughs, and webhook configurations.
Requirements
- PHP 8.1 or higher (
8.1,8.2,8.3,8.4,8.5+) ext-jsonguzzlehttp/guzzle: ^7.5
Installation
Install via Composer:
composer require wazobiamail/wazobiamail-php
Quickstart & Authentication
1. Initialization
Initialize the client with your WazobiaMail API key (wzbm_...). You can create API keys in your WazobiaMail Dashboard.
use WazobiaMail\WazobiaMailClient; // Provide API key explicitly $client = new WazobiaMailClient('wzbm_live_your_api_key_here'); // Or read automatically from environment (WAZOBIA_API_KEY or WAZOBIAMAIL_API_KEY) $client = new WazobiaMailClient();
2. Configuration Options
use WazobiaMail\WazobiaMailClient; $client = new WazobiaMailClient([ 'apiKey' => 'wzbm_live_your_api_key_here', 'baseUrl' => 'https://app.wazobiamail.com/api/v1', // Optional custom gateway 'timeout' => 45.0, // Timeout in seconds (default: 30s) ]);
Usage Guide
1. Sending Transactional Email
$result = $client->messages()->send([ 'from' => 'billing@yourdomain.com', 'to' => 'customer@example.com', 'subject' => 'November Invoice #1042', 'html' => '<h1>Your Invoice</h1><p>Thank you for your business!</p>', 'text' => "Your Invoice\nThank you for your business!", 'cc' => ['accounting@yourdomain.com'], 'attachments' => [ [ 'filename' => 'invoice-1042.pdf', 'content' => base64_encode(file_get_contents('/path/to/invoice.pdf')), 'contentType' => 'application/pdf', ], ], ], idempotencyKey: 'inv_1042_attempt_1'); // Optional idempotency key prevents duplicate sends
Deliverability & Spam Score Checking
$spamReport = $client->messages()->checkSpam([ 'from' => 'newsletter@yourdomain.com', 'subject' => 'Weekly Product Updates', 'html' => '<p>Check out our latest releases...</p>', ]); echo "Spam score: " . $spamReport['score'];
2. Domain Lifecycle & DNS Records
Provision domains and inspect the exact cryptographic records (SPF, DKIM, DMARC, MX) required for mail exchange.
// 1. Add a sending/receiving domain $domain = $client->domains()->create([ 'domain' => 'mail.mycompany.com', 'useRootDomain' => true, 'hostInboxesOnMainDomain' => true, ]); $domainId = $domain['id']; // 2. Fetch DNS records to configure at your registrar $dns = $client->domains()->getDnsRecords($domainId); echo "SPF: " . $dns['domain']['spfRecord'] . "\n"; echo "DKIM Host: " . $dns['domain']['dkimHost'] . "\n"; echo "DKIM Value: " . $dns['domain']['dkimValue'] . "\n"; // 3. Trigger asynchronous DNS verification $verifyStatus = $client->domains()->verify($domainId, force: true);
Cloudflare Auto-Sync
// Trigger automated Cloudflare DNS synchronization $client->domains()->syncCloudflare($domainId);
π Subdomain vs. Apex / Root Domain Architecture & Edge Cases
When configuring custom domains for transactional sending, choosing between a subdomain (e.g. mail.yourdomain.com or send.yourdomain.com) and an apex/root domain (yourdomain.com) is a critical architectural decision.
1. Why Using a Subdomain is Recommended (Root Domain NOT Used)
In production environments, we strongly recommend using a dedicated sending subdomain rather than your root/apex domain:
- Preserve Existing Business Email (Google Workspace / Microsoft 365):
If your root domain (
yourdomain.com) already receives corporate email via Google Workspace or Microsoft 365, adding MX records pointing to WazobiaMail at the root apex@will break your existing company email inbox routing. Usingmail.yourdomain.comisolates transactional email routing completely. - Isolate Sender Reputation:
Transactional emails (password resets, invoices, OTP codes) have high engagement and deliverability requirements. Sending from a subdomain (
mail.yourdomain.com) ensures that any accidental marketing spam complaints won't degrade your corporate employee email deliverability. - Avoid SPF 10-Lookup Limit Exhaustion (RFC 7208): SPF records have a strict limit of 10 DNS lookups. Root domains often accumulate multiple SaaS providers (Google, Zendesk, Salesforce, HubSpot). A dedicated subdomain gives WazobiaMail its own clean, independent SPF record without lookup count contention.
2. DNS Hostnames When Root Domain is NOT Used
When verifying a subdomain like mail.yourdomain.com, ensure you configure your DNS provider (Cloudflare, Route53, Namecheap, GoDaddy) with the correct hostnames:
| Record Type | Host / Name (if domain is mail.yourdomain.com) |
Target / Value | Notes |
|---|---|---|---|
| TXT (SPF) | mail (or mail.yourdomain.com) |
v=spf1 include:_spf.wazobiamail.com ~all |
Do NOT place on @ root |
| TXT (DKIM) | wzbm._domainkey.mail |
k=rsa; p=MIGfMA0GCSqGSIb3... |
Selector is scoped to the subdomain |
| MX (Hosted) | mail (priority 10 / 20) |
mx1.wazobiamail.com / mx2.wazobiamail.com |
Only required if receiving mail or hosted inboxes |
| CNAME (Tracking) | email.mail |
trk.wazobiamail.com |
Used for custom branded click/open tracking |
| TXT (DMARC) | _dmarc.mail (or fallback to _dmarc) |
v=DMARC1; p=reject; rua=mailto:dmarc@... |
Resolvers fall back to apex _dmarc if omitted |
3. Critical Edge Cases & Troubleshooting
β οΈ Edge Case 1: UnverifiedSenderDomain (HTTP 422 / 403)
- If you verified
mail.yourdomain.comin WazobiaMail, yourfromaddress must match that subdomain:- β
Valid:
'from' => 'Acme <billing@mail.yourdomain.com>' - β Invalid:
'from' => 'Acme <billing@yourdomain.com>'(Fails because apexyourdomain.comis unverified)
- β
Valid:
- Attempting to send from an unverified parent domain will throw a
ValidationExceptionorForbiddenException.
π‘ Edge Case 2: Reply-To Decoupling (Best Practice)
- You can send from the verified subdomain while routing customer replies to your corporate root domain mailbox:
$client->messages()->send([ 'from' => 'Acme Orders <orders@mail.yourdomain.com>', // Verified sending subdomain 'replyTo' => 'support@yourdomain.com', // Your corporate inbox on Google/Outlook 'to' => 'customer@example.com', 'subject' => 'Your receipt', 'html' => '<p>Thank you for your purchase!</p>', ]);
- This gives you the deliverability benefits of a dedicated sending subdomain while preserving user-friendly reply handling.
βοΈ Edge Case 3: Cloudflare Proxy Status (Grey Cloud vs. Orange Cloud)
- Problem: In Cloudflare DNS, leaving the proxy enabled (Orange Cloud) for mail or tracking records breaks SMTP traffic, DNS verification, and SSL certificate issuance.
- Solution: Always toggle Cloudflare records to "DNS only" (Grey Cloud / Proxied: Off) for all WazobiaMail SPF, DKIM, MX, and tracking records. If you use
$client->domains()->syncCloudflare($domainId), this is handled automatically.
β³ Edge Case 4: Negative DNS Caching (SOA Minimum TTL Delay)
- If you run
$client->domains()->verify($domainId)immediately after publishing DNS records before they propagate, recursive resolvers may cache the negative response (NXDOMAINor empty record). - Subsequent verification checks will continue to fail until the DNS TTL expires (often 5 to 60 minutes).
- Tip: Test propagation first with
dig TXT mail.yourdomain.comordig TXT wzbm._domainkey.mail.yourdomain.com +tracebefore triggering verification, or pass['force' => true].
π‘οΈ Edge Case 5: DMARC Subdomain Policy (sp=) Inheritance
- If your apex domain (
yourdomain.com) has a DMARC record withsp=rejectorsp=quarantine:- Any subdomain (e.g.
mail.yourdomain.com) automatically inherits that rejection policy unless it defines its own_dmarc.mail.yourdomain.comrecord. - If SPF or DKIM alignment fails on the subdomain, recipient mail servers (Gmail, Yahoo, Outlook) will reject or spam-folder your emails immediately. Ensure both SPF and DKIM pass before sending production traffic.
- Any subdomain (e.g.
π¬ Edge Case 6: Headless Inboxes & Hosted Mailboxes on Subdomains
- When provisioning hosted mailboxes (
$client->mailboxes()->create([...])), the mailbox address is generated under the specific verified domain (e.g.support@mail.yourdomain.com). - Inbound receiving requires the MX records for that exact subdomain to be pointing to WazobiaMail. Do not point the root domain MX to WazobiaMail if you have existing email services there.
π Edge Case 7: Network Timeouts & Idempotency
- If an HTTP request times out due to transient network issues, the email may already have been queued by the API Gateway.
- Always provide an
idempotencyKeywhen dispatching critical transactional messages. Retrying with the same key returns the existing tracking receipt without duplicate sending.
3. Hosted Mailbox Management
// 1. Create a hosted mailbox $mailbox = $client->mailboxes()->create([ 'domainId' => $domainId, 'localPart' => 'support', 'displayName' => 'Customer Support', 'quotaMb' => 5120, // 5 GB ]); $mailboxId = $mailbox['id']; // 2. Inspect SMTP/IMAP connection details $connection = $client->mailboxes()->getConnection($mailboxId); // 3. Issue or rotate IMAP/SMTP credentials $credentials = $client->mailboxes()->issueCredentials($mailboxId); // 4. Check total mailbox storage usage across the account $storage = $client->mailboxes()->getStorage();
4. Enterprise Mailbox Inbox API
Enterprise Plan Required: The Mailbox Messages endpoints require an active Enterprise plan. Calling these endpoints on lower tiers strictly returns a
403 Forbiddenresponse.
Inspect and process incoming customer emails programmatically without managing dedicated IMAP daemons.
use WazobiaMail\Exceptions\ForbiddenException; try { // 1. List received messages (with pagination and unread filtering) $inbox = $client->mailboxMessages()->list($mailboxId, [ 'page' => 1, 'limit' => 25, 'unreadOnly' => true, ]); foreach ($inbox['items'] as $message) { echo "From: " . $message['from'][0]['email'] . "\n"; echo "Subject: " . $message['subject'] . "\n"; echo "Preview: " . $message['preview'] . "\n"; // 2. Read full message body and attachments $detail = $client->mailboxMessages()->get($mailboxId, $message['id']); echo "HTML Body: " . $detail['htmlBody'] . "\n"; // 3. Purge processed message $client->mailboxMessages()->delete($mailboxId, $message['id']); } } catch (ForbiddenException $e) { if ($e->isEnterprisePlanRequired()) { echo "An active Enterprise plan is required to query mailbox inboxes programmatically."; } }
5. Delivery Tracking, Stats, Read Receipts & Click Receipts
WazobiaMail provides built-in, granular observability for every transactional email dispatched.
1. Read Receipts (Open Tracking)
Every HTML email sent through WazobiaMail can automatically include an invisible, privacy-compliant 1x1 tracking pixel. When the recipient opens the email in their client (Apple Mail, Gmail, Outlook, etc.):
- An
email.openedevent is recorded. - Captures exact timestamp, recipient IP geolocation, device type, and client user-agent.
- Tracks unique opens vs. total open counts to measure audience engagement.
- Queryable via
$client->tracking()->get($trackingId)or streamed via Webhooks.
2. Click Receipts (Link Tracking)
Links in your email content can be rewritten using your verified tracking domain (email.mail.yourdomain.com or trk.wazobiamail.com):
- When a recipient clicks any link, a
link.clickedreceipt is created with microsecond precision. - Records the original destination URL, recipient email, click timestamp, and click count.
- Enables fraud detection, user engagement scoring, and automated lifecycle triggers.
3. Delivery Lifecycle & Timeline
Every message transitions through well-defined lifecycle states:
queued β sent β delivered β opened β clicked β bounced / complained
// Inspect message delivery timeline and receipt counters $timeline = $client->tracking()->get('trk_abc12345'); echo "Status: " . $timeline['data']['status'] . "\n"; echo "Delivered At: " . $timeline['data']['deliveredAt'] . "\n"; echo "Read Receipts (Open Count): " . $timeline['data']['openCount'] . "\n"; echo "Click Receipts (Click Count): " . $timeline['data']['clickCount'] . "\n"; foreach ($timeline['data']['events'] as $event) { echo "[{$event['occurredAt']}] {$event['eventType']} - Recipient: {$event['recipient']}\n"; if (!empty($event['linkUrl'])) { echo " Clicked URL: {$event['linkUrl']}\n"; } } // Query aggregate delivery statistics $stats = $client->tracking()->list([ 'status' => 'delivered', 'limit' => 50, ]); echo "Total tracked: " . $stats['data']['total'] . "\n"; foreach ($stats['data']['messages'] as $msg) { echo "{$msg['trackingId']}: {$msg['status']} ({$msg['openCount']} opens, {$msg['clickCount']} clicks)\n"; }
6. Webhooks Verification
Securely verify incoming webhooks from WazobiaMail using HMAC SHA-256 signatures:
$payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_WAZOBIA_SIGNATURE'] ?? ''; $secret = getenv('WAZOBIA_WEBHOOK_SECRET'); if ($client->webhooks()->verifySignature($payload, $signature, $secret)) { $event = json_decode($payload, true); // Process event: 'email.delivered', 'email.bounced', etc. } else { http_response_code(400); exit('Invalid signature'); }
Error Handling
All SDK exceptions extend WazobiaMail\Exceptions\WazobiaException:
| Exception | HTTP Code | Description |
|---|---|---|
AuthenticationException |
401 |
Missing, invalid, or expired API key |
ForbiddenException |
403 |
Plan or permission restriction. Check $e->isEnterprisePlanRequired(). |
NotFoundException |
404 |
Requested domain, mailbox, or message was not found |
ValidationException |
422 |
Request validation failure. Inspect $e->getErrors(). |
RateLimitException |
429 |
Request rate limit exceeded. Inspect $e->getRetryAfter(). |
ServerException |
500+ |
Server error or upstream gateway outage |
use WazobiaMail\Exceptions\ValidationException; use WazobiaMail\Exceptions\ForbiddenException; use WazobiaMail\Exceptions\WazobiaException; try { $client->messages()->send([...]); } catch (ValidationException $e) { print_r($e->getErrors()); } catch (ForbiddenException $e) { echo "Forbidden: " . $e->getMessage(); } catch (WazobiaException $e) { echo "WazobiaMail error: " . $e->getMessage(); }
Testing
Run the test suite with PHPUnit:
composer test
Or directly:
vendor/bin/phpunit
License
The WazobiaMail PHP SDK is open-sourced software licensed under the MIT license.
Reading spam-check scores
The spam check returns a penalty score, a threshold, an isSpam boolean and an inverse deliverabilityScore (0β100, higher is better).
score |
rating |
isSpam |
deliverabilityScore |
Decision |
|---|---|---|---|---|
0.0 β 1.9 |
low_risk |
false |
81 β 100 |
Okay to send. Clean or only minor findings. |
2.0 β 4.9 |
moderate_risk |
false |
51 β 80 |
Review first. Not blocked, but fix the highest-point rules before sending. |
5.0 + |
high_risk |
true |
0 β 50 |
Do not send. Fix the findings and re-run the check. |
isSpam is the authoritative decision: it is true when score >= threshold. Read threshold from the response instead of hard-coding 5.0; the server can change it. A green score is not a delivery guarantee β domain authentication, sender reputation and recipient engagement still matter.
Official WazobiaMail SDKs
WazobiaMail is a Resend alternative for transactional email through an API or SMTP, with sending domains, hosted mailboxes and deliverability tools. Start free with 3,000 emails/month.
The SDKs below all use the WazobiaMail API; SMTP/legacy clients that need SMTP submission can use WazobiaMail's SMTP access from the dashboard. Choose the package that matches your stack:
| Platform | Install | Registry | Source |
|---|---|---|---|
| TypeScript / Node.js | npm install wazobiamail |
npm | GitHub |
| Vue 3 | npm install wazobiamail-vue wazobiamail |
npm | GitHub |
| Nuxt 3 & 4 | npm install wazobiamail-nuxt |
npm | GitHub |
| Python | pip install wazobiamail |
PyPI | GitHub |
| PHP | composer require wazobiamail/wazobiamail-php |
Packagist | GitHub |
| Laravel | composer require wazobiamail/wazobiamail-laravel |
Packagist | GitHub |
| Rust | cargo add wazobiamail |
crates.io | GitHub |
| Go | go get github.com/WazobiaMail/wazobiamail-go@v0.1.0 |
pkg.go.dev | GitHub |
| WordPress | Upload the plugin ZIP | Packagist Β· GitHub releases | GitHub |
What each package gives you
- TypeScript / Node.js (
wazobiamail) β A zero-production-dependency typed client for Node.js 18+, Bun, Deno and edge runtimes. It ships ESM and CommonJS builds, typed errors, request builders, URL/multipart attachments, and resources for messages, domains, mailboxes, API keys, billing, units, plans, tracking, spam checks and support. - Vue 3 (
wazobiamail-vue) β A Vue plugin plus reactive composables:useSendMailfor loading/success/error state,useMailboxfor paginated headless inboxes with polling and optimistic delete,useDomainsfor domain/DNS workflows, anduseSpamCheckfor pre-flight deliverability checks. - Nuxt 3 & 4 (
wazobiamail-nuxt) β KeepsruntimeConfig.wazobiamail.apiKeyon the Nitro server, adds the auto-importedserverWazobiaClient(event)helper, and auto-imports the Vue composables. Ideal for Nuxt apps that must not expose an API key to the browser bundle. - Python (
wazobiamail) β A synchronous, PEP 561-compliant client with zero runtime dependencies (standard library only), dataclass models and typed responses. Works in Django (sync views orsync_to_async), FastAPI (defendpoints orrun_in_threadpool), Flask, scripts and workers. Includes webhook HMAC verification. - PHP (
wazobiamail/wazobiamail-php) β A Composer/Guzzle 7 client for PHP 8.1+. It accepts ergonomic flat send payloads or the canonical nestedcontentshape, supports URL attachments and automatic multipart upload for inline files, and maps API failures to typed exceptions. - Laravel (
wazobiamail/wazobiamail-laravel) β Registers a nativewazobiamailmail driver, service provider andWazobiaMailfacade for Laravel 10β13. ExistingMail, Mailables and notifications keep working; the package also exposes domains, mailboxes and webhook verification. - Rust (
wazobiamail) β Async, strongly typed and#![forbid(unsafe_code)], built on reqwest/tokio. Includes fluent request builders, typed response models, typed errors, secret masking inDebug, and resources for sending, domains, Cloudflare DNS, mailboxes, templates, tracking and billing. - Go (
github.com/WazobiaMail/wazobiamail-go) β Context-first, concurrency-safe and zero-dependency (standard library only). Typed request/response models, API-key or Bearer auth, multipart attachments, domains, mailboxes, billing, units, plans, support, templates and HMAC webhook verification. - WordPress (
wazobiamail-wordpress) β Routes everywp_mail()call through the WazobiaMail API without changing forms or plugins. It registers the site domain, displays SPF/DKIM/DMARC/MX records, verifies DNS, uploads attachments, and keeps recent delivery receipts in the admin screen.