package Sietima; use Moo; use Sietima::Policy; use Types::Standard qw(ArrayRef Object); use Type::Params -sigs; use Sietima::Types qw(Address AddressFromStr EmailMIME Message Subscriber SubscriberFromAddress SubscriberFromStr SubscriberFromHashRef Transport); use Sietima::Message; use Sietima::Subscriber; use Email::Sender::Simple qw(); use Email::Address; use namespace::clean; with 'MooX::Traits'; # VERSION # ABSTRACT: minimal mailing list manager =head1 SYNOPSIS use Sietima; Sietima->new({ return_path => 'the-list@the-domain.tld', subscribers => [ 'person@some.were', @etc ], })->handle_mail_from_stdin; =head1 DESCRIPTION Sietima is a minimal mailing list manager written in modern Perl. It aims to be the spiritual successor of L. The base C class does very little: it just puts the email message from C into a new envelope using L<< /C >> as sender and all the L<< /C >> addresses as recipients, and sends it. Additional behaviour is provided via traits / roles. This class consumes L<< C >> to simplify composing roles: Sietima->with_traits(qw(AvoidDups NoMail))->new(\%args); These are the traits provided with the default distribution: =for :list = L<< C|Sietima::Role::AvoidDups >> prevents the sender from receiving copies of their own messages = L<< C|Sietima::Role::Debounce >> avoids mail-loops using a C header = L<< C|Sietima::Role::Headers >> adds C headers to all outgoing messages = L<< C|Sietima::Role::ManualSubscription >> specifies that to (un)subscribe, people should write to the list owner = L<< C|Sietima::Role::NoMail >> avoids sending messages to subscribers who don't want them = L<< C|Sietima::Role::NoSpoof >> replaces the C address with the list's own address = L<< C|Sietima::Role::ReplyTo >> optionally sets the C header to the mailing list address = L<< C|Sietima::Role::SubjectTag >> prepends a C<[tag]> to the subject header of outgoing messages that aren't already tagged = L<< C|Sietima::Role::SubscriberOnly::Drop >> silently drops all messages coming from addresses not subscribed to the list = L<< C|Sietima::Role::SubscriberOnly::Moderate >> holds messages coming from addresses not subscribed to the list for moderation, and provides commands to manage the moderation queue The only "configuration mechanism" currently supported is to initialise a C object in your driver script, passing all the needed values to the constructor. L<< C >> is the recommended way of doing that: it adds command-line parsing capability to Sietima. =attr C A L<< C >> instance, coerced from string if necessary. This is the address that Sietima will send messages I. =cut has return_path => ( isa => Address, is => 'ro', required => 1, coerce => AddressFromStr, ); =attr C An array-ref of L<< C >> objects, defaults to the empty array. Each item can be coerced from a string or a L<< C >> instance, or a hashref of the form { primary => $string, %other_attributes } The base Sietima class only uses the address of subscribers, but some roles use the other attributes (L<< C|Sietima::Role::NoMail >>, for example, uses the C attribute, and L<< C >> uses C via L<< C|Sietima::Subscriber/match >>) =cut my $subscribers_array = ArrayRef[ Subscriber->plus_coercions( SubscriberFromAddress, SubscriberFromStr, SubscriberFromHashRef, ) ]; has subscribers => ( isa => $subscribers_array, is => 'lazy', coerce => $subscribers_array->coercion, ); sub _build_subscribers { +[] } =attr C A L<< C >> instance, which will be used to send messages. If not passed in, Sietima uses L<< C >>'s L<< C|Email::Sender::Simple/default_transport >>. =cut has transport => ( isa => Transport, is => 'lazy', ); sub _build_transport { Email::Sender::Simple->default_transport } =method C $sietima->handle_mail_from_stdin(); This is the main entry-point when Sietima is invoked from a MTA. It will parse a L<< C >> object out of the standard input, then pass it to L<< /C >> for processing. =cut sub handle_mail_from_stdin($self,@) { my $mail_text = do { local $/; <> }; # we're hoping that, since we probably got called from an MTA/MDA, # STDIN contains a well-formed email message, addressed to us my $incoming_mail = Email::MIME->new(\$mail_text); return $self->handle_mail($incoming_mail); } =method C $sietima->handle_mail($email_mime); Main driver method: converts the given email message into a list of L<< C >> objects by calling L<< /C >>, then sends each of them by calling L<< /C >>. =cut signature_for handle_mail => ( method => Object, positional => [ EmailMIME ], ); sub handle_mail($self,$incoming_mail) { my (@outgoing_messages) = $self->munge_mail($incoming_mail); for my $outgoing_message (@outgoing_messages) { $self->send_message($outgoing_message); } return; } =method C my $subscribers_aref = $sietima->subscribers_to_send_to($email_mime); Returns an array-ref of L<< C >> objects that should receive copies of the given email message. In this base class, it just returns the value of the L<< /C >> attribute. Roles such as L<< C|Sietima::Role::AvoidDups >> modify this method to exclude some subscribers. =cut signature_for subscribers_to_send_to => ( method => Object, positional => [ EmailMIME ], ); sub subscribers_to_send_to($self,$incoming_mail) { return $self->subscribers; } =method C my @messages = $sietima->munge_mail($email_mime); Returns a list of L<< C >> objects representing the messages to send to subscribers, based on the given email message. In this base class, this method returns a single instance to send to all L<< /C >>, containing exactly the given email message. Roles such as L<< C|Sietima::Role::SubjectTag >> modify this method to alter the message. =cut signature_for munge_mail => ( method => Object, positional => [ EmailMIME ], ); sub munge_mail($self,$incoming_mail) { return Sietima::Message->new({ mail => $incoming_mail, from => $self->return_path, to => $self->subscribers_to_send_to($incoming_mail), }); } =method C $sietima->send_message($sietima_message); Sends the given L<< C >> object via the L<< /C >>, but only if the message's L specifies some recipients. =cut signature_for send_message => ( method => Object, positional => [ Message ], ); sub send_message($self,$outgoing_message) { my $envelope = $outgoing_message->envelope; if ($envelope->{to} && $envelope->{to}->@*) { $self->transport->send( $outgoing_message->mail, $envelope, ); } return; } sub _trait_namespace { 'Sietima::Role' } ## no critic(ProhibitUnusedPrivateSubroutines) =method C my $addresses_href = $sietima->list_addresses; Returns a hashref of L<< C >> instances (or things that can be passed to its constructor, like L<< C >>, L<< C >>, or strings), that declare various addresses related to this list. This base class declares only the L<< /C >>, and does not use this method at all. The L<< C|Sietima::Role::Headers >> role uses this to populate the various C headers. =cut sub list_addresses($self) { return +{ return_path => $self->return_path, }; } =method C my $app_spec_data = $sietima->command_line_spec; Returns a hashref describing the command line processing for L<< C >>. L<< C >> uses this to build the command line parser. This base class declares a single sub-command: =over =item C Invokes the L<< /C >> method. For example, in a C<.qmail> file: |/path/to/sietima send =back Roles can extend this to provide additional sub-commands and options. =cut sub command_line_spec($self) { return { name => 'sietima', title => 'a simple mailing list manager', subcommands => { send => { op => 'handle_mail_from_stdin', summary => 'send email from STDIN', }, }, }; } 1;