Search by

w4msolutions / w4m-email-gateway

W4MSOLUTIONS

Symfony Mailer transport for the W4M Email Gateway service, with Laravel and Yii2 adapters.

Package info

github.com/W4MSOLUTIONS/w4m-email-gateway

pkg:composer/w4msolutions/w4m-email-gateway

Statistics

Installs: 267

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

2.0.0 2026-10-08 18:39 UTC

This package is auto-updated.

Last update: 2026-10-08 18:50:37 UTC


README

Symfony Mailer transport that sends emails through the W4M Email Gateway API, with ready-made adapters for Laravel and Yii2.

Package

  • Name: w4msolutions/w4m-email-gateway
  • Type: library
  • Namespace: W4MSolutions\W4mEmailGateway
  • Version: 2.0.0

Supported versions

Component Supported versions
PHP 8.4 and later (^8.4)
Laravel 12.x, 13.x
Yii2 2.0.45 and later, with yiisoft/yii2-symfonymailer ^4.0
Symfony Mailer / Mime 6.4, 7.x, 8.x

The package has no hard dependency on either framework. Laravel and Yii2 are only needed by the adapter you use. Symfony 8 is only reachable from Laravel 13: yiisoft/yii2-symfonymailer 4 supports Symfony 6.4 and 7.

The test suite runs against Laravel 12 and 13, Yii2 2.0.55 and Symfony 6.4, 7.4 and 8.1.

Features

  • One delivery implementation shared by every framework: Transport\GatewayTransport, a Symfony\Component\Mailer\Transport\TransportInterface.
  • Sends payload to the gateway /send endpoint with API key authentication.
  • Converts attachments to the gateway attachments[] payload (base64 + filetype).
  • Rewrites inline CID embeds (for example cid:image.jpg) into data: URIs.
  • Retries failed sends with incremental cooldown (default: 3 attempts, waits 1s then 3s).
  • Disable flag that short-circuits delivery without an HTTP request.
  • Messages without a From address are sent with a default From (see Sender address).
  • Pluggable HTTP client (Client\GatewayClientInterface) and PSR-3 logging.

Installation

composer require w4msolutions/w4m-email-gateway

Laravel

The service provider is discovered automatically. If you disabled package discovery, register it manually:

// bootstrap/providers.php
'providers' => [
    \W4MSolutions\W4mEmailGateway\Laravel\EmailGatewayServiceProvider::class,
],

Optionally publish the config:

php artisan vendor:publish --provider="W4MSolutions\W4mEmailGateway\Laravel\EmailGatewayServiceProvider" --tag=config

Add a gateway mailer to config/mail.php:

'mailers' => [
    'gateway' => [
        'transport' => 'gateway',
        'gateway_url' => env('EMAIL_GATEWAY_URL'),
        'key' => env('EMAIL_GATEWAY_KEY'),
        'send_path' => env('EMAIL_GATEWAY_SEND_PATH', '/send'),
        'auth_header' => env('EMAIL_GATEWAY_AUTH_HEADER', 'Authorization'),
        'auth_prefix' => env('EMAIL_GATEWAY_AUTH_PREFIX', ''),
        'timeout' => (int) env('EMAIL_GATEWAY_TIMEOUT', 15),
        'retries' => (int) env('EMAIL_GATEWAY_RETRIES', 3),
        'retry_initial_delay' => (int) env('EMAIL_GATEWAY_RETRY_INITIAL_DELAY', 1),
        'retry_delay_increment' => (int) env('EMAIL_GATEWAY_RETRY_DELAY_INCREMENT', 2),
        'enabled' => ! filter_var(env('EMAIL_DISABLE', false), FILTER_VALIDATE_BOOLEAN),
    ],
],

Values missing from the mailer entry fall back to config/email-gateway.php. Note the key is gateway_url, not url: Laravel parses a mailer url key as an SMTP DSN.

Set MAIL_MAILER=gateway to make it the default, or use it explicitly:

Mail::mailer('gateway')->to($user->email)->send(new InvitationMail($user));

Failures throw Transport\GatewayTransportException after the last attempt, as any Laravel mail transport does. Queued mailables and Laravel's failover transport work as usual: a gateway failure moves a failover mailer on to the next mailer in its list.

Per-send overrides

Build a mailer with runtime configuration when a message must use a different gateway key:

$config = array_merge(config('mail.mailers.gateway'), [
    'key' => $client->email_gateway_key,
]);

Mail::build($config)->to($user->email)->send(new InvitationMail($user));

Mailers created with Mail::build() are not intercepted by Mail::fake(). The Laravel adapter sends through the Laravel HTTP client, so use Http::fake() and Http::assertSent() to assert the gateway request in tests.

Environment variables

Variable Default Description
EMAIL_GATEWAY_URL (empty) Gateway base URL
EMAIL_GATEWAY_KEY (empty) Gateway API key
EMAIL_GATEWAY_SEND_PATH /send Send endpoint path
EMAIL_GATEWAY_AUTH_HEADER Authorization Auth header name
EMAIL_GATEWAY_AUTH_PREFIX (empty) Auth header prefix, e.g. Bearer
EMAIL_GATEWAY_TIMEOUT 15 Request timeout in seconds
EMAIL_GATEWAY_RETRIES 3 Total send attempts
EMAIL_GATEWAY_RETRY_INITIAL_DELAY 1 Delay before first retry (s)
EMAIL_GATEWAY_RETRY_DELAY_INCREMENT 2 Extra delay per retry (s)
EMAIL_DISABLE false When true, delivery is a no-op
MAIL_FROM_ADDRESS noreply@host.local Default From for messages without one
MAIL_FROM_NAME (empty) Default From name for messages without one

Yii2

Requires yiisoft/yii2-symfonymailer (^4.0) in the application.

'components' => [
    'mailer' => [
        'class' => \W4MSolutions\W4mEmailGateway\Yii2\Mailer::class,
        'viewPath' => '@common/mail',
        'gatewayUrl' => 'https://mail-gateway.local',
        'gatewayApiKey' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        'authHeaderName' => 'Authorization', // Optional, default: 'Authorization'
        'authHeaderPrefix' => '', // Optional, default: ''
        'sendPath' => '/send', // Optional, default: '/send'
        'requestTimeout' => 15, // Optional, default: 15 seconds
        'maxRetries' => 3, // Optional, default: 3
        'retryInitialDelaySeconds' => 1, // Optional, default: 1
        'retryDelayIncrementSeconds' => 2, // Optional, default: 2
        'enabled' => true, // Optional, default: true; false skips the HTTP request
        'useFileTransport' => false, // Yii option; true saves .eml files and skips the gateway
    ],
],

The component extends yii\symfonymailer\Mailer, so compose, render, layouts and every yii\symfonymailer\Message call keep working. Do not set the transport option: the component builds the gateway transport itself.

send() returns false and logs through Yii::error() on any failure, following the yii\mail\MailerInterface contract; it never throws. Retry warnings go to the Yii log under the W4MSolutions\W4mEmailGateway\Yii2\Mailer category. Set the logger property to any PSR-3 logger to route them elsewhere.

Other frameworks

Anything that uses Symfony Mailer can use the transport directly:

use Symfony\Component\Mailer\Mailer;
use W4MSolutions\W4mEmailGateway\Transport\GatewayTransport;

$mailer = new Mailer(new GatewayTransport([
    'gateway_url' => 'https://mail-gateway.local',
    'key' => 'xxxxxxxx',
]));

The constructor also accepts a Client\GatewayClientInterface, a Payload\GatewayPayloadBuilder and a Psr\Log\LoggerInterface. All config keys and their defaults are listed in GatewayTransport::DEFAULTS.

Sender address

A From set on the message is always sent as from and from_name.

A message without a From is sent with a default From instead:

  • Laravel: MAIL_FROM_ADDRESS and MAIL_FROM_NAME, falling back to noreply@host.local. Override with default_from and default_from_name in config/email-gateway.php or in the mailer entry. Laravel normally sets mail.from on every message already, so this only applies when that is empty.
  • Yii2: noreply@host.local. Set a From for every message through the component's messageConfig ('messageConfig' => ['from' => [...]]).
  • Other frameworks: the default_from and default_from_name keys of the GatewayTransport config array.

What the gateway does with it depends on how it delivers:

  • Through Microsoft 365 the gateway always sends as its mailbox and ignores the From.
  • Through regular SMTP the gateway sends with that From, or returns an error when the SMTP server does not allow the address.

Retry behavior

Default retry flow is:

  1. Attempt 1 immediately.
  2. If it fails, wait 1 second.
  3. Attempt 2.
  4. If it fails, wait 3 seconds (1 + 2).
  5. Attempt 3.

Tune with retries, retry_initial_delay and retry_delay_increment (Laravel) or maxRetries, retryInitialDelaySeconds and retryDelayIncrementSeconds (Yii2).

Class reference

  • Transport\GatewayTransport: Symfony transport; validates config, maps the payload, runs the retry loop and throws GatewayTransportException after the final failure.
  • Transport\GatewayTransportException: transport failure or configuration error.
  • Payload\GatewayPayloadBuilder: maps a Symfony\Component\Mime\Email into the gateway JSON payload.
  • Payload\CidEmbedRewriter: replaces cid: references with data: URIs.
  • Client\GatewayClientInterface: HTTP POST abstraction.
  • Client\GatewayClient: default client using cURL with a stream fallback.
  • Laravel\EmailGatewayServiceProvider: registers the gateway mail transport.
  • Laravel\HttpClientGatewayClient: client built on the Laravel HTTP client.
  • Yii2\Mailer: Yii2 mailer component.
  • Yii2\YiiLogger: PSR-3 logger forwarding to the Yii log.

Running tests (Docker)

Every service installs dependencies on first run and runs as your host user (UID/GID, default 1000).

Command Runs
docker compose run --rm tests All suites
docker compose run --rm tests-unit Framework-neutral transport, payload and HTTP client
docker compose run --rm tests-laravel Laravel adapter in a Testbench application
docker compose run --rm tests-yii2 Yii2 adapter, including a Yii application with views
docker compose run --rm lint PHP_CodeSniffer with the W4M standard

Run a single file:

docker compose run --rm tests sh docker/run-tests.sh tests/Unit/GatewayTransportTest.php

Notes

  • The gateway schema documents to, cc, bcc, subject, body, attachments, from_name and reply_to. The transport also forwards a from field when the message has one.
  • Invalid messages (for example without recipients) fail before any request is sent, so a failed send never delivers.