xakki/emailer

Self-hosted transactional & notification email service with templating, tracking, subscription management and an HTTP/console API

Maintainers

Package info

github.com/Xakki/emailer

pkg:composer/xakki/emailer

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

dev-master 2026-05-24 13:41 UTC

This package is auto-updated.

Last update: 2026-05-24 13:41:42 UTC


README

CI PHP License

Self‑hosted transactional & notification email service for PHP. It stores projects, campaigns, templates and recipients in a relational database, renders HTML emails from reusable template blocks, sends them over SMTP (PHPMailer), and tracks opens, clicks and (un)subscriptions through a small set of HTTP endpoints.

The public surface is a plain PHP library (Xakki\Emailer\Emailer) plus an HTTP front controller and a console runner. There is no framework lock‑in — you wire it into your own app, container, or the bundled Docker stack.

Features

  • Projects → campaigns → templates → queue domain model on top of Doctrine DBAL 4.
  • Template engine with {{placeholder}} substitution, reusable wrapper / content / block templates and per‑project parameters.
  • SMTP delivery via PHPMailer with DKIM support and SMTP error classification (spam / quota / invalid mailbox / temporary, …).
  • Open & click tracking: tracking pixel, link rewriting, and per‑recipient statistics.
  • Subscription management: one‑click subscribe / unsubscribe endpoints, List-Unsubscribe header.
  • HTTP API (Phroute router) and a console runner for queue processing and migrations.
  • Redis caching for MX lookups and auth tokens; Doctrine Migrations for schema.
  • Tested on PHP 8.4 and 8.5, static‑analysed with PHPStan (level 7) and PSR‑12 (strict).

Requirements

  • PHP >= 8.4 with extensions: pdo, pdo_mysql, json, fileinfo, redis, intl, mbstring
  • A MySQL / MariaDB database
  • Redis
  • Composer 2

Installation

composer require xakki/emailer

Run the database migrations (against your configured connection):

./console migrations migrate

Configuration

Configuration is a plain PHP array passed to ConfigService. Only db.password is strictly required; everything else has sane defaults (see src/ConfigService.php).

use Xakki\Emailer\ConfigService;

$config = new ConfigService([
    'db' => [
        'driver'   => 'pdo_mysql',
        'host'     => '127.0.0.1',
        'port'     => 3306,
        'user'     => 'emailer',
        'password' => 'a-strong-unique-password', // required
        'dbname'   => 'emailer',
    ],
    'redis' => ['host' => '127.0.0.1', 'port' => 6379],

    // Optional: enables the read-only GET /emailer/get/{key}/{secret} accessor.
    'secret_key' => getenv('SECRET_EMAILER_KEY') ?: '',
]);
Key Default Notes
db pdo_mysql localhost set Doctrine DBAL connection params; password required
redis emailer-redis:6379 Used for MX / auth caches
route built‑in tracking routes Phroute route → [Controller, method] map
migration src/Migration Doctrine Migrations config
secret_key '' (disabled) Guards the read‑only body accessor

Usage

As a library

use Monolog\Logger;
use Xakki\Emailer\Emailer;
use Xakki\Emailer\Model\Template;
use Xakki\Emailer\Transports\Smtp;

$emailer = new Emailer($config, new Logger('emailer'));

// 1. One-time setup: project, templates, transport, notify channel, campaign.
$project = $emailer->createProject('My project', [
    Template::NAME_HOST     => 'mail.example.com',
    Template::NAME_ROUTE    => '/emailer',
    Template::NAME_LANG     => 'en',
    Template::NAME_URL_LOGO => __DIR__ . '/tpl/logo.png',
]);

$wrapper = $project->createTplWrapper('Base', file_get_contents('tpl/wrapper.html'));
$content = $project->createTplContent('News', file_get_contents('tpl/content.html'));
$notify  = $project->createNotify('Newsletter');

$smtp = new Smtp($emailer);
$smtp->fromEmail = 'robot@example.com';
$smtp->fromName  = 'Robot';
$smtp->host      = 'smtp.example.com';
$smtp->port      = 587;
$project->createTransport($smtp);

$campaign = $project->createCampaign('Welcome {{name}}', $wrapper, $content, $notify, []);

// 2. Queue a message for a recipient.
$mail = $emailer->getNewMail()
    ->setEmail('user@example.com')
    ->setEmailName('Jane Doe')
    ->setData(['name' => 'Jane']);

$hashRoute = $emailer
    ->getNewSender($campaign->project_id, $campaign->id)
    ->send($mail);

A runnable end‑to‑end example lives in example/as-vendor/init.php.

Processing the queue (console)

./console send         # send pending messages from the queue
./console newDay       # reset per-day transport counters (run daily via cron)
./console migrations migrate

HTTP tracking endpoints

The front controller (Emailer::dispatchRoute()) exposes the tracking surface. Default routes (see ConfigService::$route):

Method & path Purpose
GET /emailer/home/{key} Click‑through landing / open marker
GET /emailer/goto/{key}/{url} Tracked outbound link redirect
GET /emailer/logoimg/{key} Tracking pixel (logo image)
GET /emailer/unsubscribe/{key} One‑click unsubscribe
GET /emailer/subscribe/{key} Re‑subscribe
GET /emailer/status/{key} Per‑message delivery status page
GET /emailer/get/{key}/{secret} Read‑only rendered body (secret‑gated; opaque 404 when secret_key is unset)
echo $emailer->dispatchRoute($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);

The JSON management API (login / dashboard / SMTP test) is documented in swagger.json.

Docker

A full dev stack (PHP‑FPM, Nginx, MariaDB, Redis) is provided:

cp .env_dist .env   # edit passwords
make build
make up

See the Makefile for migrations-*, phpunit, phpstan, cs-* targets.

Development & quality

The CI matrix runs the whole tool‑chain on PHP 8.4 and 8.5. To reproduce locally you only need PHP with the extensions listed above plus Composer:

composer install

composer test          # PHPUnit
composer test-coverage # PHPUnit + HTML/text coverage (needs Xdebug or PCOV)
composer phpstan       # PHPStan level 7 (src + tests)
composer cs-check      # PSR-12 strict (squizlabs/php_codesniffer)
composer cs-fix        # auto-fix style

Current line coverage is ~70%. Tests live in tests/ and split into pure unit tests (mocked DBAL connection) and integration tests that run against an in‑memory SQLite database (tests/Support/IntegrationCase.php).

Project layout

src/
  Emailer.php          Entry point / service locator
  ConfigService.php    Typed configuration
  Mail.php             Outgoing message value object
  Sender.php           Queues a message for a campaign
  Controller/          HTTP + console + JSON API controllers
  Model/               Active-record style domain models
  Repository/          Doctrine DBAL data access
  Cqrs/                Single-purpose command/query handlers
  Transports/          SMTP transport (PHPMailer)
  Migration/           Doctrine schema migration
  Helper/, Exception/, locale/, view/
tests/                 PHPUnit unit + integration suites
example/               Runnable usage examples
docker/                Local dev & CI images

Contributing

Contributions are welcome — please read CONTRIBUTING.md first.

License

Distributed under the GNU General Public License v3.0 or later. See LICENSE.