Search by

onlime / sendmail-wrapper

onlime

A powerful sendmail wrapper to log and throttle emails sent by PHP

Package info

github.com/onlime/sendmail-wrapper

pkg:composer/onlime/sendmail-wrapper

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 26

Open Issues: 1

2.0.0 2026-11-08 00:16 UTC

This package is auto-updated.

Last update: 2026-11-08 00:19:42 UTC


README

A powerful sendmail wrapper to log and throttle emails sent by PHP

Advantages

  • Lets you monitor any mail traffic from PHP scripts
  • Allows throttling (limiting) emails sent by PHP's mail() function
  • Throttle by sent email and/or recipient count per day
  • Logs both to syslog and database with message metadata
  • Logs common mail headers like From, To, Cc, Bcc, Subject
  • Fixes Return-Path header on the fly for users who did not correctly set it
  • Highly secured setup, customers cannot access the logging/throttling database
  • Standalone PHP application without any external library dependencies (no Composer vendor/ required in production)
  • Built for shared webhosting environments where PHP runs in PHP-FPM (FastCGI Process Manager) mode
  • No cronjobs required, sendmail-wrapper will reset counters automatically every day

Requirements

  • PHP 8.2+ (CLI, /usr/bin/php) with extensions mbstring, pdo_mysql and posix
  • sendmail compatible MTA: Exim, Postfix,...
  • sudo 1.9.10+ (Debian 12+), for regular expressions in sudoers
  • MySQL 8.0+ (or Percona Server) or MariaDB 11.8+

Currently, sendmail-wrapper is tested and actively used by Onlime GmbH on shared webhosting environments with PHP 8.2, 8.3, 8.4 and 8.5, and tested with MySQL 8.0, 8.4, 9.7 and MariaDB 11.8, 12.3.

prepend.php is prepended to every PHP request of every PHP version installed on your server and therefore still supports PHP 7.4+.

Installation

Initial Setup

Clone repository from GitHub:

$ git clone https://github.com/onlime/sendmail-wrapper.git /opt/sendmail-wrapper

Setup system user for sendmail-wrapper:

$ adduser --system --home /no/home --no-create-home --uid 6000 --group --disabled-password --disabled-login sendmailwrapper
$ adduser sendmailwrapper customers

Quick Install

The installer script install.sh will correctly set up permissions and symlink the wrapper scripts:

$ cd /opt/sendmail-wrapper/
$ ./install.sh

If you wish to run this manually, check the following instructions...

Manual Install

Set correct permissions:

$ chown root:root *.php *.ini
$ chown root:sendmailwrapper sendmail-throttle.php config.private.ini
$ chmod 440 config.private.ini
$ chmod 444 config.ini config.local.ini
$ chmod 555 sendmail-wrapper.php prepend.php
$ chmod 550 sendmail-throttle.php
$ chmod 400 schema/*.sql

IMPORTANT: Never make the sendmailwrapper user the owner of any file. A file owner can always change its permissions and modify it, and sendmail-wrapper.php and prepend.php are executed by all your customers.

Create symlinks:

$ ln -sf /opt/sendmail-wrapper/sendmail-wrapper.php /usr/sbin/sendmail-wrapper
$ ln -sf /opt/sendmail-wrapper/sendmail-throttle.php /usr/sbin/sendmail-throttle
$ install -o root -g root -m 444 prepend.php /var/www/shared/prepend.php

Setup sudo

Add the following lines to your /etc/sudoers (or a file in /etc/sudoers.d/), restricting the only argument to the recipient count:

www-data        ALL = (sendmailwrapper) NOPASSWD:/usr/sbin/sendmail-throttle ^[0-9]+$
%customers      ALL = (sendmailwrapper) NOPASSWD:/usr/sbin/sendmail-throttle ^[0-9]+$

NOTE: Don't use a regular expression like ^[0-9]{1,6}$: commas separate commands in sudoers, so the rule would silently be split.

Setup PHP

Add/modify the following in your php.ini:

sendmail_path = /usr/sbin/sendmail-wrapper -t -i
auto_prepend_file = /var/www/shared/prepend.php

NOTE: It is recommended to put the default -t -i options in the sendmail_path directive of your php.ini instead of appending them directly to the sendmailCmd config option in your config.local.ini.

This way, it won't break any projects that use Symfony Mailer component which actually checks for -bs or -t in sendmail_path. (see SendmailTransport.php)

Setup MySQL

Import the sendmailwrapper database schema:

$ mysql -u root -p < schema/schema.mysql.sql

Create a MySQL user with the following permissions:

CREATE USER sendmailwrapper@'localhost' IDENTIFIED BY '********';
GRANT SELECT, INSERT, UPDATE ON sendmailwrapper.throttle TO sendmailwrapper@'localhost';
GRANT INSERT ON sendmailwrapper.messages TO sendmailwrapper@'localhost';

Configuration

Default configuration can be found in config.ini:

[global]
defaultTZ = Europe/Zurich
adminTo = hostmaster@example.com
adminFrom = hostmaster@example.com

[syslog]
stringLengthLimit = 120
stringCutSuffix = "..."

[wrapper]
sendmailCmd = "/usr/sbin/sendmail"
throttleCmd ="sudo -u sendmailwrapper /usr/sbin/sendmail-throttle"
throttleOn = true
defaultHost = "example.com"
syslogPrefix = sendmail-wrapper-php
xHeaderPrefix = "X-Example-"

[throttle]
countMax = 1000
rcptMax = 1000
syslogPrefix = sendmail-throttle-php
adminSubject = "Sendmail limit exceeded"
ignoreExceptions = false

[db]
dsn = "mysql:host=localhost;dbname=sendmailwrapper;charset=utf8mb4"
user = sendmailwrapper
pass = "xxxxxxxxxxxxxxxxxxxxx"

throttle.ignoreExceptions = true lets messages pass if throttling fails, e.g. if the database server is not available. Otherwise, such messages are rejected.

You should not change any of the above values. Create your own config.local.ini instead to overwrite some values, e.g.:

[global]
adminTo = hostmaster@mydomain.com
adminFrom = hostmaster@mydomain.com

[wrapper]
defaultHost = "mydomain.com"
xHeaderPrefix = "X-MyCompany-"

Never put your database password in any of the above configuration files. Use another configuration file called config.private.ini instead, e.g.:

[db]
pass = "mySuper-SecurePassword/826.4287+foo"

Development

Install the development dependencies (only required for development, never in production) and run the Pest test suite and Pint code style fixer:

$ composer install
$ composer test
$ composer lint

The tests use fake sendmail/throttle commands and an in-memory SQLite database, so no MTA, sudo or MySQL server is required.

To run the throttle tests against MySQL or MariaDB as well (as in CI), pass a DSN without database name. The tests drop and recreate the sendmailwrapper_test database, so never use a production server:

$ docker run -d --rm --name sendmailwrapper-test-db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root mysql:8.4
$ TEST_MYSQL_DSN="mysql:host=127.0.0.1;port=3306;charset=utf8mb4" TEST_MYSQL_USER=root TEST_MYSQL_PASSWORD=root composer test

Upgrading

Upgrade from 1.1.x to 2.0.x

  • PHP 8.2+ is required for the PHP CLI (/usr/bin/php) that runs sendmail-wrapper. ext-imap is no longer required, but ext-posix is.
  • Re-run install.sh (or update your permissions manually, see Manual Install): all files must be owned by root.
  • Convert your database to utf8mb4 (during low traffic, as converting the messages table blocks writes while running), and add charset=utf8mb4 to your DSN if you have overridden db.dsn in your config.local.ini:
    $ mysql -u root -p < schema/migrations/20261107-utf8mb4.sql
  • Add an index on throttle.username, so that locking a user's throttle record does not block all other users:
    $ mysql -u root -p < schema/migrations/20261107-add-username-index.sql
  • sudo 1.9.10+ (Debian 12+) is required: restrict the sudoers rules to a single number with a regular expression, see Setup sudo. The former wildcard [0-9]* also matched additional arguments.
  • messages.sender_host now contains the fully qualified hostname of the server running sendmail-wrapper (like hostname -f), instead of the client host seen by the MySQL server (USER()). On the database server itself, this is its hostname instead of localhost.
  • sendmail-wrapper now returns the exit status of sendmail, so mail() returns false if the MTA failed to accept a message.

Upgrade from 1.0.x to 1.1.x

To avoid problems with projects that use Symfony Mailer (Laravel's Mail component also uses Symfony Mailer under the hood!), we have moved the default sendmail command-line options -t -i from Sendmail-wrapper's configuration config.ini to the recommended sendmail_path directive in php.ini. If you stick with our default configuration, you need to update your php.ini:

- sendmail_path = /usr/sbin/sendmail-wrapper
+ sendmail_path = /usr/sbin/sendmail-wrapper -t -i

If you don't care about Symfony Mailer or any other mailer components that check for -t existence in sendmail_path, you can keep the old php.ini configuration and add this to your config.local.ini:

sendmailCmd = "/usr/sbin/sendmail -t -i"