Schedule messages to be transmitted when you want them to go, including messages with attachments.
This plugin lets users schedule emails to be sent later. It includes a web UI, localization, and CLI helpers to trigger a queue worker that delivers messages when they’re due.
This guide covers both Composer-based and manual installation, database setup, configuration, and setting up the worker trigger (cron/systemd).
- Roundcube (modern version; plugin uses standard
rcube_pluginAPI). - PHP compatible with your Roundcube (PHP 8.x recommended).
- Database access for creating the plugin table.
- Ability to run a periodic job (cron or systemd timer) to call the worker endpoint.
- Web access to your Roundcube URL for the worker (or curl from cron).
-
Place the plugin in a VCS or local path that Composer can reference.
-
Ensure your Roundcube root has the Roundcube plugin installer in
require(most distros do):"require": { "roundcube/plugin-installer": "^0.3" }
-
Add a repository that points to the plugin (adjust the path):
"repositories": [ { "type": "path", "url": "../scheduled_sending_composer" } ], "require": { "texxasrulez/scheduled_sending": "*" }
-
Run:
composer install # or composer require texxasrulez/scheduled_sending:*
Composer will install the plugin under
plugins/scheduled_sending(per itscomposer.json).
- Unzip the plugin into Roundcube’s plugins directory:
cd /path/to/roundcube unzip /tmp/scheduled_sending.zip -d plugins/scheduled_sending - Ensure permissions match your web server user:
chown -R www-data:www-data plugins/scheduled_sending (or tailor to your server's user:group) find plugins/scheduled_sending -type d -exec chmod 755 {} \; find plugins/scheduled_sending -type f -exec chmod 644 {} \;
Create the queue table (MySQL/MariaDB):
- File:
plugins/scheduled_sending/SQL/mysql.initial.sql - SQL (for convenience):
CREATE TABLE `scheduled_queue` (
`id` bigint(20) UNSIGNED NOT NULL,
`user_id` bigint(20) UNSIGNED NOT NULL,
`identity_id` bigint(20) UNSIGNED NOT NULL DEFAULT 0,
`scheduled_at` datetime NOT NULL,
`status` varchar(16) NOT NULL DEFAULT 'queued',
`raw_mime` mediumtext DEFAULT NULL,
`meta_json` text DEFAULT NULL,
`dedupe_key` varchar(64) DEFAULT NULL,
`last_error` text DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `scheduled_queue`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `uq_scheduled_dedupe` (`dedupe_key`),
ADD KEY `idx_sched_at` (`scheduled_at`),
ADD KEY `idx_status` (`status`);
ALTER TABLE `scheduled_queue`
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;
COMMIT;If you use a database other than MySQL/MariaDB, adapt the SQL accordingly.
Copy the example config and edit it:
cd plugins/scheduled_sending
cp config.inc.php.dist config.inc.phpKey options (from config.inc.php.dist):
$config['scheduled_sending_table'] = 'scheduled_queue';
$config['scheduled_worker_batch'] = 20;
$config['scheduled_timezone'] = 'America/Chicago'; // optional; storage is UTC
$config['scheduled_sending_logging'] = false; // plugin log entries
$config['scheduled_debug'] = false;
$config['scheduled_force_plugin_assets'] = false;
$config['scheduled_show_fab'] = true;
$config['scheduled_sending_worker_token'] = '32_character_key';
$config['scheduled_sending_delivery'] = 'smtp'; // 'smtp', 'mail', or 'none' for dry-run
$config['scheduled_sending_batch'] = 25; // optional
$config['scheduled_sending_sent_folder'] = 'Sent'; // optional
$config['db_table_scheduled_sending'] = 'scheduled_queue';
$config['scheduled_sending_lock_key'] = 'scheduled_sending_worker';
$config['scheduled_sending_lock_timeout'] = 10; // secondsImportant: set a strong scheduled_sending_worker_token (32+ random characters).
Set scheduled_sending_logging to true to write normal plugin and worker entries to the scheduled_sending Roundcube log. Set scheduled_debug to true only when you need the more detailed diagnostic trace written to scheduled_sending_debug.
When scheduled_sending_delivery is smtp, scheduled rows store the scheduling user's SMTP username and encrypted SMTP password in meta_json when available. This lets the worker send later with the same Roundcube SMTP credentials in multi-user installations.
Edit your Roundcube main config (e.g. config/config.inc.php) and add the plugin name:
// Add 'scheduled_sending' to the plugins array
$config['plugins'] = array_merge($config['plugins'] ?? [], ['scheduled_sending']);Clear Roundcube caches if needed (e.g., remove temp/* & cache/* contents, keeping the dirs).
The plugin exposes an HTTP worker action that sends all messages scheduled at or before “now”. The worker requires the token from your config.inc.php.
Worker URL shape:
https://YOUR-ROUNDCUBE-BASE/?_task=login&_action=plugin.scheduled_sending.worker&_token=32_character_key
There are two convenient ways to call it periodically:
bin/scheduled_queue_worker.phpmakes an HTTP request to the worker URL.bin/scheduled_send.phpis a shim that requires the worker script.
Examples:
# Using CLI flags
php plugins/scheduled_sending/bin/scheduled_queue_worker.php --url="https://mail.example.com/roundcube/" --token="32_character_key"
# Or via environment variables
SS_WORKER_URL="https://mail.example.com/roundcube/" SS_WORKER_TOKEN="32_character_key" php plugins/scheduled_sending/bin/scheduled_queue_worker.phpCron: run once per minute (or every 5 minutes if your use-case is lax):
* * * * * SS_WORKER_URL="https://mail.example.com/roundcube/" SS_WORKER_TOKEN="32_character_key" php /var/www/roundcube/plugins/scheduled_sending/bin/scheduled_queue_worker.php >> /var/log/roundcube/scheduled_worker.log 2>&1curl -fsS "https://mail.example.com/roundcube/?_task=login&_action=plugin.scheduled_sending.worker&_token=32_character_key"Cron variant:
* * * * * curl -fsS "https://mail.example.com/roundcube/?_task=login&_action=plugin.scheduled_sending.worker&_token=32_character_key" >> /var/log/roundcube/scheduled_worker.log 2>&1The worker is idempotent and gated by a lock (
scheduled_sending_lock_key/ timeout) to avoid overlap.
- Log in to Roundcube, compose a message, pick a future time, and schedule.
- Confirm a row is added to the
scheduled_queuetable. - Ensure your cron/systemd job runs; the message should send at/after the scheduled time.
- Check logs:
- Web server access/error logs
logs/or a dedicated log (the plugin writes viarcube::write_log('scheduled_sending', ...)whenscheduled_sending_loggingis enabled)
- Confirm sent messages land in the configured Sent folder (if set).
- 401/403 on worker call: bad or missing
_token. Verifyscheduled_sending_worker_tokenmatches the value you pass. - Nothing gets sent: verify the cron is running and the URL points to your Roundcube base. Check that due items exist in
scheduled_queueandstatusisqueued. - Timezones: UI may use
scheduled_timezone; storage is UTC. Ensure your server clock is correct (NTP) and PHPdate.timezoneis set. - Mail transport:
scheduled_sending_deliveryuses Roundcube’s SMTP by default. If usingmailor a relay, ensure it’s configured correctly in Roundcube. - SMTP credentials: queued SMTP messages rely on the encrypted credential values captured in
meta_jsonat schedule time. If a user changes their SMTP password before the worker runs, reschedule the message. - Locks: if you see messages about an active lock, either reduce the cron frequency or increase
scheduled_sending_lock_timeout.
- Remove the plugin folder or uninstall via Composer.
- Optionally drop the table:
DROP TABLE IF EXISTS `scheduled_queue`;
scheduled_sending.php— main plugin class (registers actions/UI)config.inc.php.dist— example configurationSQL/mysql.initial.sql— schema for the queue tablebin/scheduled_queue_worker.php— CLI helper (HTTP to worker)bin/scheduled_send.php— thin wrapper including the worker helpertemplates/— Roundcube templates for UIlocalization/— i18n stringsskins//js/— assets
That’s it. Once the table exists, the config is set (especially _token), and the cron/systemd job is running, scheduled emails will go out on time.
Enjoy!
💰 Donations 💰
If you use this plugin and would like to show your appreciation by buying me a cup of coffee, I surely would appreciate it. A regular cup of Joe is sufficient, but a Starbucks Coffee would be better ...
Zelle (Zelle is integrated within many major banks Mobile Apps by default) - Just send to texxasrulez at yahoo dot com
No Zelle in your banks mobile app, no problem, just click Paypal and I can make a Starbucks run ...
I appreciate the interest in this plugin and hope all the best ...
scheduled_sendingnow keeps its canonical version inscheduled_sending::PLUGIN_VERSIONinsidescheduled_sending.php.scheduled_sending::info()exposes the plugin metadata array used for self-identification.- Development builds should use a
+devsuffix such as1.0.0+dev. - Release builds should use a clean tagged version such as
1.0.0.
For a release bump:
- Update
scheduled_sending::PLUGIN_VERSIONinscheduled_sending.phpor runsh scripts/bump-version.sh 1.0.0. - Update
CHANGELOG.md. - Create the matching release tag after verification.