action-mailbox
Action Mailbox
Action Mailbox routes inbound email into mailbox classes. A raw message enters through an ingress, becomes an inbound-email record with a processing state machine, is matched to a mailbox by recipient, sender, or subject, and is processed with callbacks.
Inbound email
parse-message turns a raw RFC 822 string into a Message with unfolded
headers and a body:
use MVC::Keayl::Mailbox;
my $message = parse-message($raw);
$message.from; # the sender address
$message.to; # the recipient addresses
$message.cc;
$message.recipients; # to + cc
$message.subject;
$message.message-id;
$message.header('x-spam-score');
$message.body;An InboundEmail wraps the raw message and tracks its state. It starts
pending and transitions through processing, then delivered, bounced, or
failed:
my $email = InboundEmail.new(:$raw);
$email.is-pending; # True
$email.processing-bang;
$email.delivered-bang; # or bounced-bang / failed-bang
$email.message; # the parsed MessageMailboxes
A mailbox subclasses Mailbox and implements process. Callbacks run around
processing, and the inbound email is marked delivered when process returns:
class SupportMailbox is Mailbox {
method process {
# self.message is the parsed inbound message
}
}
SupportMailbox.before-processing(-> $mailbox { ... });
SupportMailbox.after-processing(-> $mailbox { ... });perform-processing drives the lifecycle: it marks the email processing, runs
the before callbacks, calls process, runs the after callbacks, and marks the
email delivered.
Bouncing
Calling bounce inside processing marks the email bounced, runs the
on-bounce callbacks, and halts the rest of processing (the after callbacks are
skipped):
class RepliesMailbox is Mailbox {
method process {
self.bounce unless self.message.from;
}
}
RepliesMailbox.on-bounce(-> $mailbox { ... });Failures
An exception raised during processing marks the email failed. A matching
rescue-from handler runs; with no handler, the exception is re-raised:
class ImportMailbox is Mailbox {
method process { ... }
}
ImportMailbox.rescue-from(X::Parse, -> $mailbox, $error { ... });Routing
A Router maps inbound email to a mailbox. Each routing rule matches on
recipient, to, from, or subject, takes a matching predicate over the
inbound email, or all for a catch-all. Conditions accept an exact string or a
regex (subject and a string match as a substring); every condition in a rule
must match. The first matching rule wins:
use MVC::Keayl::Mailbox::Router;
my $router = Router.new;
$router.routing(to => '[email protected]', mailbox => SupportMailbox);
$router.routing(from => rx:i/ '@billing.example.com' $/, mailbox => BillingMailbox);
$router.routing(subject => 'invoice', mailbox => InvoiceMailbox);
$router.routing(matching => -> $email { $email.message.header('x-spam').defined }, mailbox => SpamMailbox);
$router.routing(all => True, mailbox => DefaultMailbox);
$router.route($email); # finds the mailbox and processes the email
$router.mailbox-for($email); # the matched mailbox class, or NilIngress
An ingress receives raw email, records an InboundEmail, and hands it to the
router. RelayIngress takes one message at a time, as from a webhook relay:
use MVC::Keayl::Mailbox::Ingress;
my $ingress = RelayIngress.new(:$router);
$ingress.receive($raw); # records and routes the message
$ingress.repository.all; # every recorded inbound emailSourceIngress pulls from a generic source (an SMTP or POP mailbox), processing
every fetched message:
my $ingress = SourceIngress.new(:$router, :$source);
$ingress.poll; # fetches from source.fetch and processes eachRecords persist through a repository.
MVC::Keayl::Mailbox::Ingress::Repository defines the interface and
MemoryRepository is the in-process implementation.
Relay endpoint
RelayController is the HTTP relay endpoint. Configure the ingress once, and the
controller hands each posted raw email to it:
set-mailbox-ingress(RelayIngress.new(:$router));POSTing a raw message to the controller returns 204; with no configured
ingress it returns 503.