onlime / sendmail-wrapper
A powerful sendmail wrapper to log and throttle emails sent by PHP
Requires
- php: ^8.2
- ext-mbstring: *
- ext-pdo: *
- ext-posix: *
Requires (Dev)
- laravel/pint: ^1.20
- pestphp/pest: ^3.8 || ^4.0 || ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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-Pathheader 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 extensionsmbstring,pdo_mysqlandposix - 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.phpis 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
sendmailwrapperuser the owner of any file. A file owner can always change its permissions and modify it, andsendmail-wrapper.phpandprepend.phpare 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 -ioptions in thesendmail_pathdirective of your php.ini instead of appending them directly to thesendmailCmdconfig option in yourconfig.local.ini.This way, it won't break any projects that use Symfony Mailer component which actually checks for
-bsor-tinsendmail_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-imapis no longer required, butext-posixis. - 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
messagestable blocks writes while running), and addcharset=utf8mb4to your DSN if you have overriddendb.dsnin yourconfig.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_hostnow contains the fully qualified hostname of the server running sendmail-wrapper (likehostname -f), instead of the client host seen by the MySQL server (USER()). On the database server itself, this is its hostname instead oflocalhost.- sendmail-wrapper now returns the exit status of sendmail, so
mail()returnsfalseif 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"