## Problem
Sentry reporting permission-denied errors on `/open-banking/callback`:
> UnexpectedValueException: The stream or file
"/app/storage/logs/laravel.log" could not be opened in append mode:
Failed to open stream: Permission denied
4 issues, 14 events (PHP-LARAVEL-Z, 10, 11, 12).
## Root cause
Entrypoint runs as root. After the initial `chown www-data`, artisan
commands (`config:cache`, `route:cache`, `view:cache`, `event:cache`,
`migrate`) execute and can create/touch `storage/logs/laravel.log` as
root. Later, php-fpm (www-data) tries to append and fails.
Queue workers + inertia-ssr also run as root via supervisor — same
failure mode from worker side.
## Fix
- `docker/entrypoint.sh`: re-chown `/app/storage` and
`/app/bootstrap/cache` to www-data **after** artisan cache warm-up, just
before `exec supervisord`.
- `docker/supervisor/supervisord.conf`: add `user=www-data` to
`queue-worker`, `queue-worker-emails`, `inertia-ssr`. Nginx and php-fpm
master keep root (need port 80 / pool forking).
## Verification
Deploy and confirm Sentry issues stop firing on next
`/open-banking/callback` hits.
## Summary
- Implement event-driven drip email campaign system for new user signups
- Add 5 targeted emails based on user journey (welcome, onboarding
reminder, promo code, import help, feedback)
- Configure Laravel Horizon with Redis queue for reliable job processing
- Track sent emails in `user_mail_logs` table to prevent duplicates
## Email Schedule
| Email | Timing | Condition |
|-------|--------|-----------|
| Welcome | 30 min after signup | All users |
| Onboarding Reminder | 1 day after signup | Not onboarded |
| Promo Code (FOUNDER) | 1 day after signup | Onboarded + has
transactions + not subscribed |
| Import Help | 1 day after signup | Onboarded + no transactions + not
subscribed |
| Feedback | 5 days after signup | Not subscribed |
## Architecture
```
User Registers → ScheduleDripEmailsListener
├─> SendWelcomeEmailJob (30 min delay)
├─> SendOnboardingReminderEmailJob (1 day delay)
├─> SendPromoCodeEmailJob (1 day delay)
├─> SendImportHelpEmailJob (1 day delay)
└─> SendFeedbackEmailJob (5 days delay)
```
Each job checks conditions at execution time and either sends the email
+ logs it, or silently completes.
## Test plan
- [x] All 23 drip email tests pass
- [x] Migration creates `user_mail_logs` table
- [x] Jobs dispatch with correct delays
- [x] Emails only sent when conditions are met
- [x] Duplicate emails prevented via UserMailLog check
- [ ] Verify Horizon processes jobs in production