arzcode / sisifo
Sisifo — autonomous email/inbox watcher and summarizer as a Filament v4/v5 plugin.
Requires
- php: ^8.4
- beyondcode/laravel-mailbox: ^6.0
- filament/filament: ^4.7|^5.2
- filament/spatie-laravel-settings-plugin: ^4.7|^5.2
- laravel/ai: ^1.0
- laravel/framework: ^13.0
- league/html-to-markdown: ^5.1
- spatie/laravel-settings: ^3.0
- webklex/laravel-imap: ^6.2
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.2
- nunomaduro/collision: ^8.1
- orchestra/testbench: ^11.0
- pestphp/pest: ^5.0
- pestphp/pest-plugin-laravel: ^5.0
- rector/rector: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Filament v4/v5 plugin that turns an IMAP mailbox into an autonomous, LLM-powered watcher and summarizer.
Sisifo polls a single IMAP account, persists incoming messages, and runs scheduled or watch-style mailbox tasks that ask an LLM (through the Laravel AI SDK by default) to summarize, classify, or extract information from new emails — then dispatches notifications through configurable channels (Pushover, Filament database notifications, …).
Like its namesake Sisyphus, it keeps rolling through the inbox so you don't have to.
Features
- IMAP polling — fetches unseen messages on a schedule and stores them as
InboundEmailrecords. - Two task types
- Summary — runs on a daily/hourly schedule and digests everything new since it last ran.
- Watch — fires continuously, reacting to matching emails as soon as they arrive.
- LLM-driven processing via a swappable
LlmProvidercontract (Laravel AI SDK driver included). - Filament resource to create and manage tasks from your panel.
- Pluggable notification channels — Pushover and Filament database notifications out of the box.
- Per-task filtering by sender address, sender domain, subject keywords, and look-back window.
- Smart digests — tasks remember their last result so the LLM can highlight only what's new, and can stay silent when there's nothing worth reporting.
- One-shot tasks that deactivate themselves after firing once.
- English and Spanish translations included.
Requirements
- PHP 8.4+
- Laravel 13
- Filament 4.7+ or 5.2+
- An IMAP-accessible mailbox
- An LLM provider supported by the Laravel AI SDK (Anthropic by default)
Installation
composer require arzcode/sisifo
Register the plugin in your Filament panel:
use Arzcode\Sisifo\SisifoPlugin; $panel->plugin(SisifoPlugin::make());
Run the migrations (they are autoloaded by the package, so this is all you need):
php artisan migrate
Publishing is optional and only needed when you want to customize things:
php artisan vendor:publish --tag=sisifo-config # config/sisifo.php php artisan vendor:publish --tag=sisifo-migrations # database/migrations php artisan vendor:publish --tag=sisifo-settings-migrations # spatie settings migrations php artisan vendor:publish --tag=sisifo-lang # lang/vendor/sisifo php artisan vendor:publish --tag=sisifo-views # resources/views/vendor/sisifo
Configuration
Most settings can be driven by environment variables; see config/sisifo.php for the full list and defaults.
IMAP
SISIFO_IMAP_HOST=imap.example.com SISIFO_IMAP_PORT=993 SISIFO_IMAP_ENCRYPTION=ssl SISIFO_IMAP_USERNAME=watcher@example.com SISIFO_IMAP_PASSWORD=secret SISIFO_IMAP_TIMEOUT=60 # socket timeout in seconds SISIFO_IMAP_RETRY_ATTEMPTS=3 # attempts per fetch before giving up SISIFO_IMAP_RETRY_DELAY_SECONDS=5 # base backoff, multiplied by the attempt number
Transient connection failures
webklex/php-imap collapses every socket-level failure during LOGIN — a dropped
connection, a read timeout — into a single AuthFailedException("failed to authenticate"), which reads like rejected credentials but is not: a genuinely
rejected login surfaces as ImapServerErrorException with the server's NO
response instead.
Sisifo therefore retries those transient failures (retry_attempts, with a linear
backoff), never retries a server-side rejection, and logs the full exception chain
so the actual cause (RuntimeException: empty response, say) reaches your error
tracker instead of the misleading top-level message.
The fetch throttle is armed after every check, successful or not. Without that, a single failed connection would make the every-minute scheduled command reconnect each minute until it succeeded — which is exactly how a provider like Fastmail starts rate limiting the account. Use the Force fetch action on a watch task to bypass the throttle on demand.
LLM
SISIFO_LLM_DRIVER=laravel-ai # the only bundled driver SISIFO_LLM_PROVIDER=anthropic SISIFO_LLM_MODEL=claude-haiku-5-5 SISIFO_LLM_MAX_TOKENS=2048
The laravel-ai driver delegates to the Laravel AI SDK, which is installed with the package. SISIFO_LLM_PROVIDER is the name of a provider in config/ai.php, so configure its credentials as the SDK expects — for Anthropic, setting ANTHROPIC_API_KEY is enough; publishing config/ai.php is optional.
Schedule
SISIFO_CHECK_EVERY_MINUTES=15 # how often to actually hit IMAP
The package registers the mailbox:process command to run every minute (in the environments listed under sisifo.schedule.environments, default production). The command itself throttles the IMAP fetch to check_every_minutes, while task scheduling is evaluated each run. Make sure Laravel's scheduler is active:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Holidays
If the host app has a holidays table and today's date (in the app timezone) appears in its date column, mailbox:process skips every scheduled task for the day. The mailbox is still polled, so the emails received on a holiday are handled on the next working day. Running a single task with --task ignores holidays, as it ignores the schedule.
The Mailbox Tasks list in the panel says so under its heading: that tasks are skipped today, or otherwise the date of the next holiday.
Apps without that table are unaffected. Point the check at a different table or column if yours are named otherwise:
SISIFO_HOLIDAYS_TABLE=holidays SISIFO_HOLIDAYS_DATE_COLUMN=date
Set sisifo.holidays.table to null to turn the check off.
Notifications
// config/sisifo.php 'notifications' => [ // Closure returning the recipient model for database notifications. 'notifiable' => fn () => \App\Models\User::find(config('sisifo.notify_user_id')), 'channels' => [ \Arzcode\Sisifo\Notifications\Channels\DatabaseNotificationChannel::class, \Arzcode\Sisifo\Notifications\Channels\PushoverChannel::class, ], ],
SISIFO_NOTIFY_USER_ID=1
Usage
Create a Mailbox Task from the Filament resource the plugin registers. Each task has:
- Type —
Summary(scheduled digest) orWatch(continuous). - Prompt — the instructions handed to the LLM for this task.
- Schedule (summary tasks) —
dailyorhourly, with optional days of week, time, and timezone. - Filters —
from_addresses,from_domains,subject_keywords,look_back_days(default 7), andignore_read(off by default) to skip emails that are no longer unread in the inbox — read, answered, moved or deleted — as of the last mailbox poll. - Notification methods — any combination of the configured channels.
- Urgent flag, and a one-shot flag to auto-deactivate after a single run.
A shared common prompt (stored in settings) is appended to every task's prompt — handy for global tone/format instructions.
If, after analyzing the emails, the LLM decides there is nothing relevant to report, the task stays silent (no notification is sent).
Running manually
php artisan mailbox:process # fetch + run all due tasks php artisan mailbox:process --task=5 # run a single task by ID, ignoring its schedule
Extending
Custom LLM provider
Implement the contract and bind it:
use Arzcode\Sisifo\Contracts\LlmProvider; class MyLlmProvider implements LlmProvider { public function text(string $instructions, string $input, ?int $maxTokens = null): string { // ... } } // In a service provider: $this->app->bind(LlmProvider::class, MyLlmProvider::class);
Custom notification channel
use Arzcode\Sisifo\Contracts\NotificationChannel; use Arzcode\Sisifo\Models\MailboxTask; class SlackChannel implements NotificationChannel { public function send(MailboxTask $task, string $title, string $body, bool $urgent): void { // ... } }
Then add it to sisifo.notifications.channels (and, if it should be selectable per task, to MailboxTaskNotificationEnum).
Note: the
EmbeddingStorecontract and its drivers are scaffolding for a future memory feature and are not yet wired into mailbox tasks.
Translations
English (en) and Spanish (es) are bundled. Publish sisifo-lang to override strings or add locales.
Testing
composer test
Changelog
See CHANGELOG.md for what has changed recently.
License
MIT. See LICENSE.md.