Email — Deployment
Production deploy (migrate-only)
php artisan migrate --force
php artisan optimizeThis ships:
- Schema for
email_accounts,email_folders,email_labels,email_message_label,email_messages,email_attachments,email_signatures,email_templates,email_message_links - Catalog row for free opt-in module
email(data migration — not default-included); later migrations bump catalog version (e.g. 1.1.0 shared templates, 1.2.0 EloSync labels, 1.3.0 reading-pane layout + multi-select bulk API) viaDefaultModuleRegistrar::bumpVersion - Additive permission grants for default roles (
email.*) - Additive template column
is_shared(existing rows backfilled private)
Do not run db:seed, CatalogSeeder, or any permission seeder in production for this module.
PHP IMAP extension
Personal sync uses PHP’s native imap_* API (NativeImapMailboxClient). Ensure ext-imap is enabled on every app and queue host that runs sync/connect:
php -m | grep -i imapWithout the extension, account test/sync fails with a clear runtime error. Do not rely on Composer IMAP packages for v1.
On PHP 8.4+, IMAP is unbundled (PECL). Windows / Laravel Herd:
- Download the matching NTS build from PECL imap Windows releases (e.g.
php_imap-1.0.3-8.4-nts-vs17-x64.zipfor Herd PHP 8.4 NTS x64). - Copy
php_imap.dllinto the PHPextdirectory (Herd:%USERPROFILE%\.config\herd\bin\php84\ext\). - Add
extension=imapto that version’sphp.ini, thenherd restart. - Confirm with
php -m(must listimap).
Queue worker (email-sync)
Sync and outbound personal send jobs use the email-sync queue (not the platform emails notification queue).
Local / Supervisor
php artisan queue:work redis --queue=email-sync --sleep=1 --tries=3 --timeout=300 --max-time=3600Keep personal mailbox jobs separate from transactional emails / emails-high capacity. After credential or queue config changes, restart workers.
Laravel Forge daemon (recommended)
Add a second API daemon (do not mix into the notifications worker):
| Field | Value |
|---|---|
| Command | php artisan queue:work redis --queue=email-sync --sleep=1 --tries=3 --timeout=300 --max-time=3600 |
| User | forge |
| Directory | Same as the API release root (…/current or site path) |
| Processes | 1 (scale if many concurrent mailbox syncs) |
Ensure PHP ext-imap is enabled on the Forge server (see above). Deploy scripts should already run queue:restart so both daemons pick up new code.
Scheduler
Enable Forge Scheduler (schedule:run every minute). The Email module registers:
php artisan email:syncevery minute with withoutOverlapping + onOneServer. The command only dispatches SyncEmailAccountJob for accounts that are due based on each mailbox’s sync_interval_minutes (default 5, options 5 / 10 / 15 / 30 / 60). Manual Sync from the SPA always dispatches immediately.
* * * * * php /path/to/artisan schedule:runAttachment storage (deferred)
The email_attachments table and client helpers exist for a future download/compose path. v1 sync does not persist attachment files to disk; it may set has_attachments from IMAP headers only. Prefer a private disk and monitor growth when that path ships.
Personal attachments are unrelated to platform email log body storage (EMAIL_LOGS_STORE_BODY).
What the data migrations do (and do not do)
register_email_module
Uses App\Support\Catalog\DefaultModuleRegistrar:
- Ensures Communication category +
emailcatalog row if missing is_default_included=false,is_billable=false, monthly/yearly0- Does not auto-install for existing or new workspaces
- Never overwrites commercial flags if the row already exists
add_email_permissions
Uses App\Support\Permissions\TenantPermissionSynchronizer:
- Creates missing
email.*vocabulary - Grants missing defaults to mapped roles (additive only)
- Never
syncPermissions()/ never revokes customizations
New workspaces
Email is not in installDefaultModules(). Owners install from Marketplace when needed. RBAC vocabulary exists after migrate; roles receive mapped grants from the permission data migration / provisioning maps.
Verification
php artisan test --compact tests/Feature/Tenant/Email
php -m | grep -i imapFrontend (optional):
cd EloSync-Frontend
npm run test:e2e:emailSmoke after deploy:
- Marketplace shows Email as free to install
- After install +
email.accounts.manage, user can open/emailand see Connect your mailbox - With
ext-imap+ worker onemail-sync, test connection and sync succeed for app-password mailboxes - Settings → Mail and notification queues remain unchanged
- User A cannot list User B’s accounts or messages
Related
- Platform production runbook
- Module development — production
- Developer guide
- Multi-Provider Email — transactional mail queues (
emails)