A declarative PHP DSL for defining email rule matching and automated actions using IMAP.
- Declarative syntax for defining email rules
- Flexible matchers for sender, recipient, and subject filtering
- Pattern matching with wildcards and regular expressions
- Logical combinators (AND, OR, NOT) for complex rule logic
- Built-in actions for common email operations
- Composable helpers for chaining multiple actions
- Type-safe with full PHP 8.3+ type hints
composer require aegypius/mailbox-rulesCreate a rules.php file:
<?php
use MailboxRules\Action\MoveToFolder;
use MailboxRules\Action\MarkAsRead;
use function MailboxRules\{mailbox, rule, from, chain};
return mailbox('imap://user:pass@imap.example.com', [
rule(
name: "Archive Newsletters",
when: from("*@newsletters.com"),
then: static fn () => chain(
new MoveToFolder("Newsletters"),
new MarkAsRead()
)
),
]);Rules are defined using the rule() function with three parameters:
rule(
name: "Rule Name", // Human-readable rule name
when: $matcher, // Matcher that evaluates the message
then: static fn () => ... // Closure that yields actions
)Matchers evaluate whether a message matches specific criteria.
rule(
name: "Log Everything",
when: any(),
then: static fn () => yield new LogAction()
)// Exact match
from("sender@example.com")
// Wildcard domain
from("*@example.com")
// Wildcard local part
from("newsletter-*@site.com")
// Regex pattern
from("/^admin@.*/i")// Matches if any recipient matches the pattern
to("support@example.com")
to("*@team.example.com")
to("/^.*@(support|help)\.com$/i")// Exact match
subject("Important Message")
// Wildcard
subject("*[Newsletter]*")
subject("Order #*")
// Regex
subject("/^\\[URGENT\\].*/i")Combine multiple matchers with logical operators.
All matchers must match:
rule(
name: "Important Team Emails",
when: allOf(
from("*@company.com"),
subject("*[Team]*")
),
then: static fn () => yield new Flag()
)At least one matcher must match:
rule(
name: "Multiple Senders",
when: anyOf(
from("*@vendor1.com"),
from("*@vendor2.com"),
from("*@vendor3.com")
),
then: static fn () => yield new MoveToFolder("Vendors")
)Negates a matcher:
rule(
name: "Not From Spam",
when: not(from("*@spam.com")),
then: static fn () => yield new MoveToFolder("Inbox")
)Combine logical operators for complex rules:
rule(
name: "Spam Filter",
when: allOf(
anyOf(
from("*@spam.com"),
subject("*Get rich quick*")
),
not(subject("*Order Confirmation*"))
),
then: static fn () => yield new MoveToFolder("Spam")
)Actions are executed when a rule matches. They implement the Action interface with an __invoke(Message $message): void method.
new MoveToFolder("Archive")
new MoveToFolder("Archive", expunge: true)Moves the message to the specified folder. Set expunge: true to permanently remove from the source folder.
new MarkAsRead()Marks the message as read (sets the \Seen flag).
new Flag()Flags the message (sets the \Flagged flag).
new LogAction()Logs message details to stdout.
Execute multiple actions in sequence:
rule(
name: "Process Newsletter",
when: subject("*[Newsletter]*"),
then: static fn () => chain(
new MoveToFolder("Newsletters"),
new MarkAsRead(),
new LogAction()
)
)Without chain(), you would need to yield each action individually:
then: static fn () {
yield new MoveToFolder("Newsletters");
yield new MarkAsRead();
yield new LogAction();
}The DSL supports three pattern matching modes:
from("user@example.com")Matches the exact string.
from("*@example.com") // Any sender from example.com
from("newsletter-*@*.com") // newsletter- prefix, any .com domain
subject("*[Important]*") // Contains [Important]*matches any sequence of characters- Wildcards can appear anywhere in the pattern
from("/^admin@.*/i") // Case-insensitive admin emails
subject("/^\\[URGENT\\]\\s.*/") // Subject starts with [URGENT]- Pattern must start and end with
/ - Supports all PHP regex modifiers (i, m, s, etc.)
rule(
name: "Move Spam",
when: anyOf(
from("*@spam.com"),
subject("*viagra*"),
subject("*lottery*")
),
then: static fn () => yield new MoveToFolder("Spam")
)rule(
name: "Archive and Read Newsletters",
when: allOf(
anyOf(
from("*@newsletters.com"),
subject("*[Newsletter]*")
),
not(subject("*Unsubscribe*"))
),
then: static fn () => chain(
new MoveToFolder("Newsletters"),
new MarkAsRead()
)
)rule(
name: "Flag Team Mentions",
when: allOf(
to("team@example.com"),
subject("*@yourname*")
),
then: static fn () => chain(
new Flag(),
new LogAction()
)
)rule(
name: "Vendor Emails",
when: anyOf(
from("*@vendor1.com"),
from("*@vendor2.com"),
from("*@vendor3.com")
),
then: static fn () => yield new MoveToFolder("Vendors")
)rule(
name: "Smart Spam Filter",
when: allOf(
anyOf(
from("*@spam.com"),
from("*@junk.net"),
subject("*click here*")
),
not(anyOf(
subject("*Order Confirmation*"),
subject("*Password Reset*"),
from("*@trusted.com")
))
),
then: static fn () => yield new MoveToFolder("Spam")
)Create custom actions by implementing the Action interface:
<?php
namespace MailboxRules\Action;
use DirectoryTree\ImapEngine\Message;
use MailboxRules\Action;
final readonly class CustomAction implements Action
{
public function __construct(
private string $parameter,
) {
}
public function __invoke(Message $message): void
{
// Your custom logic here
// Access message methods: $message->subject(), $message->from(), etc.
}
}Use it in rules:
rule(
name: "Custom Processing",
when: from("*@example.com"),
then: static fn () => yield new CustomAction("param")
)Run the test suite:
composer testRun PHPStan static analysis:
composer phpstanRun code style checks:
composer cs-checkInstall git hooks:
pre-commit install --install-hooks -t pre-commit -t commit-msg- PHP 8.3 or higher
- ext-imap
- DirectoryTree/ImapEngine
MIT
Contributions are welcome! Please follow the existing code style and add tests for new features.