NAME
Punk::Plugin::Mailer - outbound mail for a Punk application
SYNOPSIS
package MyApp;
use Punk;
host 'https://example.com';
plugin 'Mailer' => {
transport => 'smtp',
from => 'Example <ops@example.com>',
mail_dir => 'root/mail',
smtp => { host => 'mail.example.com', username => 'ops@example.com',
password => secret('smtp_password') },
};
post '/contact' => sub {
my ($c) = @_;
my $r = $c->mail(to => 'help@example.com', subject => 'Contact form',
text => $c->param('message'));
return $c->text($r->accepted ? 'sent' : 'not sent: ' . $r->message);
};
post '/signup' => sub {
my ($c) = @_;
my $user = $c->model('User')->create({ email => $c->param('email') });
my ($r, $link) = $c->mail_token($user, kind => 'verify',
subject => 'Verify your address', template => 'verify');
return $c->render('check-your-mail', { sent => $r->accepted });
};
DESCRIPTION
Wires Punk::Mailer into an application: one transport, defaults for the sender, templates from a directory, a hand-off to Punk::Queue for sending outside the request, and the one-line way to mail a Punk::Auth token. Every option of every layer is checked when plugin runs, so a typo or a missing credential stops the boot.
CONFIGURATION
plugin 'Mailer' => {
transport => 'smtp', # required: smtp, resend, sendmail, capture, log
from => 'Example <ops@example.com>',
reply_to => 'help@example.com',
message_id_domain => 'example.com',
base => 'https://example.com', # for links; defaults to the host keyword
mail_dir => 'root/mail', # templates; optional
layout => 'layout', # layout.txt.tmpl / layout.html.tmpl; optional
later => { task => 'mail.send', queue => 'mail', attempts => 5 },
later_inline_max => 1_048_576,
smtp => {...}, resend => {...}, sendmail => {...}, capture => {...}, log => {...},
};
transport and the per-transport hashes are Punk::Mailer's own options and are handed to it unchanged. In punk.yml the same mapping goes under plugins: Mailer:.
base
The origin absolute links are built on. A request's Host header is never used for this - it is whatever the client sent - so links come from configuration: base, or the application's host keyword, which may be declared after the plugin and is read at to_app. Without either, mail_url and mail_token croak when called.
mail_dir and templates
A directory of Template::Stencil templates, one message per name: name.txt.tmpl for the text part, name.html.tmpl for the HTML alternative, either or both. A message that names a template gets whichever exist; the HTML side is rendered with escaping on and the text side with it off. With layout, each part is wrapped by layout.txt.tmpl / layout.html.tmpl with the rendered part as body (use raw body in the HTML layout; it is already escaped).
The render data is the message's data plus base, subject, to, and locale - the negotiated language tag when Punk::Plugin::I18n is registered. Translated strings are the handler's to put in data, as they are for a page. The directory is read once at registration; a template that names nothing there is a croak listing what does exist.
later
Sends through Punk::Queue instead of in the request. later takes the task name (default mail.send) and any job defaults the queue understands (queue, attempts, priority). It needs the Queue plugin registered before this one: the task is declared through the task keyword when plugin 'Mailer' runs, and croaks if the keyword is not there.
HELPERS
$c->mail(%message)
my $result = $c->mail(to => ..., subject => ..., text => ...);
my $result = $c->mail(to => ..., subject => ..., template => 'welcome',
data => { name => $user->{name} });
Punk::Mailer's message, with three keys of the plugin's own: template and data render the body, and later => 1 hands the message to the queue. Sends now and returns a Punk::Mailer::Result; never dies for what happened on the wire, only for a malformed message.
$c->mail_later(%message)
The same message, queued. Returns the job id. The message is rendered now - the user, the language and the host live in the request and a job has none of them - and an attachment that is a Punk::Upload is made durable first, because the upload's temp file is gone when the request ends: with Punk::Plugin::Blob registered it is stored by contents and the job reads it by path; otherwise it is read into the job's arguments, up to later_inline_max, and over that it croaks naming the Blob plugin. An attachment given as a path you own is left alone - keep the file until the job has run.
The job body is Punk::Mailer::Job::send: an accepted Result is the job's result; deferred and failed die so the queue's retry policy applies; rejected notes final on the job and dies, because no retry will change a 5xx - size the queue's attempts knowing a permanent rejection wastes a few.
$c->mail_template($name, \%data)
my $parts = $c->mail_template('welcome', { name => 'Ann' });
# { text => ..., html => ... }, undef for a kind that has no file
$c->mail_url($path)
base joined to a path starting with /. Croaks with no base.
$c->mail_token($user, %options)
my ($result, $link) = $c->mail_token($user,
kind => 'verify', # required: the token kind
ttl => 2 * 24 * 60 * 60, # default two days
path => '/verify/%s', # %s is the token
subject => 'Verify your address', # required
template => 'verify', # required; data gets link, token, user
to => $user->{email}, # default: the user's email field
later => 1, # optional
);
Issues a single-use token through Punk::Auth's issue_token (croaks without the auth keyword), builds the link on base, renders and sends. Returns the Result (or the job id) and the link - the link so a development page can show it when no mail is configured. Auth's rule applies: issuing a new token of a kind invalidates the user's older ones, so a re-sent mail leaves exactly one live link. Redeem it with $c->take_token.
CLASS METHODS
engine_for
my $mailer = Punk::Plugin::Mailer->engine_for('MyApp');
The Punk::Mailer the class registered, for code that has the class and no context - a job body in a site, a maintenance script.
state_for
my $cfg = Punk::Plugin::Mailer::state_for('MyApp');
The plugin's configuration for a class, as a live hash - the introspection seam, and what a test reaches for.
register
What plugin 'Mailer' calls. Not for calling directly.
SEE ALSO
Punk::Mailer, Punk::Mailer::Result, Punk::Mailer::Transport, Punk::Queue, Punk::Auth.