Search by

wazobiamail / wazobiamail-laravel

oyenet1

Send Laravel mail through the WazobiaMail API. WazobiaMail is a Resend alternative that also supports SMTP, with domains, hosted mailboxes, and deliverability tooling.

Package info

github.com/WazobiaMail/wazobiamail-laravel

Homepage

pkg:composer/wazobiamail/wazobiamail-laravel

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-11-11 00:59 UTC

This package is auto-updated.

Last update: 2026-11-11 00:59:57 UTC


README

Latest Version Total Downloads License Docs

Official Laravel integration package for WazobiaMail. Provides seamless integration with Laravel's native Mail manager, custom wazobiamail mail driver, an expressive WazobiaMail Facade, and full support for Enterprise features (Mailbox Inboxes, Domain DNS auto-sync, Webhooks).

📚 Official Platform Documentation: Visit docs.wazobiamail.com for exhaustive API references, architecture guides, DNS setup walkthroughs, and webhook configurations.

Requirements

  • PHP: ^8.1 || ^8.2 || ^8.3 || ^8.4 || ^8.5
  • Laravel Framework: ^10.0 || ^11.0 || ^12.0 || ^13.0

Installation

Install the package via Composer:

composer require wazobiamail/wazobiamail-laravel

The package uses Laravel's package auto-discovery, so its WazobiaMailServiceProvider and WazobiaMail facade will register automatically.

Configuration

1. Publish Configuration

Publish the wazobiamail.php config file:

php artisan vendor:publish --tag=wazobiamail-config

This creates config/wazobiamail.php:

return [
    'api_key' => env('WAZOBIA_API_KEY'),
    'base_url' => env('WAZOBIA_BASE_URL', 'https://app.wazobiamail.com/api/v1'),
    'timeout' => (float) env('WAZOBIA_TIMEOUT', 30.0),
];

2. Environment Variables

Add your WazobiaMail API key to your .env file:

WAZOBIA_API_KEY=wzbm_live_your_api_key_here
MAIL_MAILER=wazobiamail

3. Mail Configuration

In config/mail.php, ensure the wazobiamail mailer is registered:

'mailers' => [
    'wazobiamail' => [
        'transport' => 'wazobiamail',
    ],
    // ...
],

Usage

1. Native Laravel Mail

When MAIL_MAILER=wazobiamail is set in your .env, all standard Laravel mail functions (Mails, Mailables, Notifications) are routed through WazobiaMail's high-speed API:

use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;

Mail::to('customer@example.com')->send(new WelcomeEmail());

Or using raw emails:

Mail::raw('Welcome to our service!', function ($message) {
    $message->to('customer@example.com')
            ->subject('Welcome!')
            ->from('hello@yourdomain.com', 'Our Company');
});

2. WazobiaMail Facade

You can also use the WazobiaMail facade to interact directly with the WazobiaMail REST API resources:

use WazobiaMail\Laravel\Facades\WazobiaMail;

Sending Messages Directly

$response = WazobiaMail::messages()->send([
    'from' => 'Team <noreply@yourdomain.com>',
    'to' => ['client@example.com'],
    'subject' => 'Monthly Statement',
    'html' => '<p>Here is your statement</p>',
    'text' => 'Here is your statement',
]);

echo $response['id']; // msg_...

Enterprise Inbox & Mailbox Messages

Read and manage messages stored in user mailboxes:

// List inbox messages
$inbox = WazobiaMail::mailboxMessages()->list('mb_12345', [
    'folder' => 'INBOX',
    'page' => 1,
    'per_page' => 20,
]);

// Fetch a single message with body & attachments
$email = WazobiaMail::mailboxMessages()->get('mb_12345', 'msg_98765');

// Delete a message
WazobiaMail::mailboxMessages()->delete('mb_12345', 'msg_98765');

Managing Mailboxes

// Create mailbox
$mailbox = WazobiaMail::mailboxes()->create([
    'domain_id' => 'dom_12345',
    'email_prefix' => 'support',
    'password' => 'StrongPassword123!',
    'quota_mb' => 2048,
]);

// List mailboxes
$mailboxes = WazobiaMail::mailboxes()->list();

// Rotate password
WazobiaMail::mailboxes()->updatePassword('mb_12345', 'NewStrongPassword456!');

Sending Domains & DNS Verification

// Register a new domain
$domain = WazobiaMail::domains()->create([
    'domain_name' => 'notifications.example.com',
]);

// Get DNS setup records (SPF, DKIM, MX, DMARC)
$dns = WazobiaMail::domains()->getDnsRecords('dom_12345');

// Trigger verification
$status = WazobiaMail::domains()->verify('dom_12345');
🌐 Subdomain vs. Apex / Root Domain Architecture & Edge Cases

When configuring custom domains for transactional sending, choosing between a subdomain (e.g. mail.example.com or notifications.example.com) and an apex/root domain (example.com) is a critical architectural decision.

  • Preserve Existing Business Email (Google Workspace / Microsoft 365): If your root domain (example.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. Using mail.example.com isolates 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.example.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.
DNS Hostnames When Root Domain is NOT Used

When verifying a subdomain like notifications.example.com, ensure you configure your DNS provider (Cloudflare, Route53, Namecheap, GoDaddy) with the correct hostnames:

Record Type Host / Name (if domain is notifications.example.com) Target / Value Notes
TXT (SPF) notifications v=spf1 include:_spf.wazobiamail.com ~all Do NOT place on @ root
TXT (DKIM) wzbm._domainkey.notifications k=rsa; p=MIGfMA0GCSqGSIb3... Selector is scoped to the subdomain
MX (Hosted) notifications (priority 10 / 20) mx1.wazobiamail.com / mx2.wazobiamail.com Only required if receiving mail or hosted inboxes
CNAME (Tracking) email.notifications trk.wazobiamail.com Used for custom branded click/open tracking
TXT (DMARC) _dmarc.notifications (or fallback to _dmarc) v=DMARC1; p=reject; rua=mailto:dmarc@... Resolvers fall back to apex _dmarc if omitted
Critical Edge Cases & Troubleshooting
  • ⚠️ UnverifiedSenderDomain (HTTP 422 / 403): If you verified notifications.example.com in WazobiaMail, your from address in your Mailable must match that subdomain (e.g. notifications@notifications.example.com). Attempting to send from an unverified root domain will throw a ValidationException.
  • 💡 Reply-To Decoupling (Best Practice): You can send from the verified subdomain while routing customer replies to your corporate root domain mailbox:
    Mail::to($user->email)->send(
        (new OrderShipped($order))
            ->from('orders@notifications.example.com', 'Store Orders')
            ->replyTo('support@example.com', 'Customer Support')
    );
  • ☁️ Cloudflare Proxy Status (Grey Cloud vs. Orange Cloud): In Cloudflare DNS, leaving the proxy enabled (Orange Cloud) for mail or tracking records breaks SMTP traffic and DNS verification. Always toggle Cloudflare records to "DNS only" (Grey Cloud / Proxied: Off).
  • ⏳ Negative DNS Caching (SOA Minimum TTL Delay): If you trigger domain verification before DNS propagates globally, recursive resolvers cache negative lookups (NXDOMAIN). Wait 5 to 60 minutes or check dig TXT notifications.example.com before triggering verification.
  • 🛡️ DMARC Subdomain Policy (sp=) Inheritance: If your apex domain (example.com) has a DMARC record with sp=reject, subdomains automatically inherit that rejection policy. Ensure both SPF and DKIM pass on the subdomain before sending production traffic.
  • 📬 Hosted Mailboxes on Subdomains: When provisioning hosted mailboxes, mailboxes are created under the specific verified domain (e.g. support@notifications.example.com). Inbound receiving requires the MX records for that exact subdomain to point to WazobiaMail.
  • 🔁 Network Timeouts & Idempotency: Provide an idempotencyKey when dispatching critical transactional messages to prevent duplicate delivery on retry.

Delivery Tracking, Stats, Read Receipts & Click Receipts

WazobiaMail provides built-in, granular observability for every transactional email dispatched.

  • Read Receipts (Open Tracking): An invisible 1x1 tracking pixel records when recipients open your email, capturing timestamp, IP geolocation, user-agent, and open counts.
  • Click Receipts (Link Tracking): Links in HTML content are automatically rewritten to your tracking domain, logging each click event, recipient, destination URL, and click counts.
  • Delivery Lifecycle: Real-time status progression from queued ➔ sent ➔ delivered ➔ opened ➔ clicked ➔ bounced.
// Inspect single message timeline and receipt counters
$timeline = WazobiaMail::tracking()->timeline('msg_abc123');

echo "Status: " . $timeline['data']['status'] . "\n";
echo "Read Receipts (Opens): " . $timeline['data']['openCount'] . "\n";
echo "Click Receipts (Clicks): " . $timeline['data']['clickCount'] . "\n";

// Query delivery events
$events = WazobiaMail::tracking()->events([
    'event_type' => 'delivered',
    'recipient' => 'client@example.com',
]);

Webhook Handling & Verification

In your Laravel webhook controller:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use WazobiaMail\Laravel\Facades\WazobiaMail;
use WazobiaMail\Exceptions\ValidationException;

class WazobiaWebhookController extends Controller
{
    public function handle(Request $request)
    {
        $signature = $request->header('X-Wazobia-Signature');
        $timestamp = (int) $request->header('X-Wazobia-Timestamp');
        $secret = config('services.wazobia.webhook_secret');

        try {
            $event = WazobiaMail::webhooks()->constructEvent(
                payload: $request->getContent(),
                signature: $signature,
                timestamp: $timestamp,
                secret: $secret
            );
        } catch (ValidationException $e) {
            return response()->json(['error' => 'Invalid signature'], 400);
        }

        match ($event['event']) {
            'delivered' => logger('Email delivered to ' . $event['recipient']),
            'bounced'   => logger('Email bounced: ' . $event['reason']),
            default     => null,
        };

        return response()->json(['received' => true]);
    }
}

Error Handling

The package throws specific, typed exceptions that extend WazobiaMail\Exceptions\WazobiaException:

Exception HTTP Status Description
AuthenticationException 401 Invalid or missing API key
ForbiddenException 403 Plan restriction or missing permissions. Call $e->isEnterprisePlanRequired() to check if an Enterprise upgrade is needed.
NotFoundException 404 Requested resource (domain, mailbox, message) was not found
ValidationException 422 Payload failed schema validation. Retrieve field errors via $e->getErrors().
RateLimitException 429 Rate limit exceeded. Call $e->getRetryAfter() for retry window.
ServerException 500+ WazobiaMail internal or upstream gateway error

Testing

Run the test suite using PHPUnit:

composer test

Security Vulnerabilities

If you discover any security vulnerabilities, please send an e-mail to security@wazobiamail.com.

License

The WazobiaMail Laravel package is open-sourced software licensed under the MIT license.