documenti
DESCRIPTION
An overview of Email::Sender and Email::MIME::Kit, new Perl libraries to help ease the pain of dealing with email.TRANSCRIPT
the 2009 PEP talk!!!
...I work there!
YAPC::NA 2006
PEP: Thoughts from PoboxYAPC::NA 2006
YAPC::NA 2007
How I Learned To Stop Worrying and Love Email
YAPC::NA 2007
YAPC::NA 2008
EmailYAPC::NA 2008
hates the living!
What’s the best way to deal with horrible code?
Write more code!
...so I did! The last year has been really productive. Lots of obnoxious problems were sorted out. I’m really happy with what we got accomplished, and so finally I can with a straight face...
rjbs<3
emailthe 2009 PEP talk!!!
sending email
MIME::LiteMail::Send
Mail::SenderMail::Sendmail
so, here are some of the libraries for sending mail. these all let you specify subject, to/from, and content -- so you can’t actually make an email just hwo you want and pass it in. worthless for any real use
Mail::Mailer
this lets you send mail through pluggable backends, and you provide the mail message. not bad, but not great; it has this weird API where it isa IO::Handle and you print to it like a filehandle to send your message. subclassing is a pain because it’s a blessed globref
Email::Send
Email::Send simplifies by expecting a string, being a hashref, and being in theory easier to extend (by using Module::Pluggable)unfortunately, this pluggability (and some other oddities) ends up making extending things harder and can cause it to silently lose mail. for real.
this is probably about about as good as a number of them.
system(“sendmail @opts < $tempfile”) && die sprintf “sendmail died: %s”, errstr($?);
this is probably about about as good as a number of them.
Mail::Transport
So, Mail::Transport isn’t bad. It’s part of the Mail-Box distro, which means it tends to get things right, but it also tends to be confusing and overwhelming for new users. If you’re already using it happily, that’s great! We weren’t, though, and so we wrote something new, built using just the ideas we liked from Email::Send, and
Email::Sender
Email::Sender
Email::Sender
Email-Sender uses Møøse
Email::Sender
Email-Sender uses MøøseEmail::Sender is a role
Email::Sender
Email-Sender uses MøøseEmail::Sender is a roleyou use Email::Sender::Simple
Email::Sender::Simple
use Email::Sender::Simple qw(sendmail);
my $email = $customer->build_welcome_email;
sendmail($email);
Yup. That’s about it. That will try to send mail with either sendmail or SMTP, depending on what’s available. If it can’t send the message, it will throw.
use Email::Sender::Simple qw(sendmail);
my $email = $customer->build_welcome_email;
sendmail( $email, { to => \@rcpts, from => $sender, });
Email::Sender::Simple
In other words, you can specify the envelope separately from the header. This is of *vital* importance to real email work. VERP, mailing lists, especially.
Email::Sender::Simple
my $smtp = Email::Sender::Transport::SMTP->new({ host => ‘sasl.pobox.com’, port => 26,});
sendmail($email, { transport => $smtp });
If you don’t like the auto-detected transport, you can specify one. Transports are really easy to write, and a bunch already exist:
Transports
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SMTP
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SMTP
Persist. SMTP
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SMTP
Persist. SMTP
Maildir, Mbox
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SMTP
Persist. SMTP
Maildir, Mbox
DevNull
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SMTP
Persist. SMTP
Maildir, Mbox
DevNull
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SMTP
Persist. SMTP
Maildir, Mbox
DevNull
Test
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
TransportsSendmail
SMTP
Persist. SMTP
Maildir, Mbox
DevNull
Test
SQLite
SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.
Email::Sender::Simple
my $smtp = Email::Sender::Transport::SMTP->new({ host => ‘sasl.pobox.com’, port => 26,});
sendmail($email, { transport => $smtp });
so, you can do this, that’s fine, and you can use any of those cool transports when you call sendmail...but what about if you want to change the whole default?
Email::Sender::Simple
walrus!rjbs:~$ EMAIL_SENDER_TRANSPORT=Maildir my-awesome-program --auden
now *every* call to Email::Sender::Simple->send will deliver to ./Maildir so you can see exactly what it would have sent
Programmable Failure
so, for example, we have a system that distributes mail across a cluster of hosts with various mechanisms to cope with failure
Failable lets me test them by using a mailer that always works wrapped in predictable failure, easily
Programmable Failure
::Failable transport
so, for example, we have a system that distributes mail across a cluster of hosts with various mechanisms to cope with failure
Failable lets me test them by using a mailer that always works wrapped in predictable failure, easily
Programmable Failure
::Failable transportwrap any transport
so, for example, we have a system that distributes mail across a cluster of hosts with various mechanisms to cope with failure
Failable lets me test them by using a mailer that always works wrapped in predictable failure, easily
Programmable Failure
::Failable transportwrap any transportmake it fail when you want
so, for example, we have a system that distributes mail across a cluster of hosts with various mechanisms to cope with failure
Failable lets me test them by using a mailer that always works wrapped in predictable failure, easily
Other Random Senderisms
Other Random Senderisms
structured failure
Other Random Senderisms
structured failurepartial successes
making email messages
Email::Simple
Email::MIME
these are pretty low level
you probably want to send only a few messages
(but with differences)
MIME Crap to Deal With
MIME Crap to Deal With
header encoding
MIME Crap to Deal With
header encodingcontent encoding
MIME Crap to Deal With
header encodingcontent encodingbuilding html & plain parts
MIME Crap to Deal With
header encodingcontent encodingbuilding html & plain partsattaching files
Other Crap to Deal With
Other Crap to Deal With
templated documents
Other Crap to Deal With
templated documentsvalidate parameters
Other Crap to Deal With
templated documentsvalidate parametersreusable hunks of content
hate!
Existing Solutions
Existing Solutionsnone?
Existing Solutionsnone?horrible hacks
Existing Solutionsnone?horrible hacks
make a template in TT
Existing Solutionsnone?horrible hacks
make a template in TTrender to html
Existing Solutionsnone?horrible hacks
make a template in TTrender to htmlhtml-to-text
Existing Solutionsnone?horrible hacks
make a template in TTrender to htmlhtml-to-texthope you guess right at headers
Email::MIME::Kit
Email::MIME::Kit
Email::MIME::Kit
a bunch of files
Email::MIME::Kit
a bunch of fileswith instructions on how to assemble them
Email::MIME::Kit
a bunch of fileswith instructions on how to assemble themand some other data
Kit Manifest{ "renderer" : "TT", "header" : [ { "Subject": "Hello [% user.name %]" }, { "From": "Test Sender <[email protected]>" }, { "To": "[% user.email %]" } ], "alternatives": [ { "type": "text/plain", "path": "body.txt" }, { "type": "text/html", "path": "body.html" } ], "attachments": [ { "path": "demands.rtf" } ]}
Assembling the Kit
my $kit = Email::MIME::Kit->new({ source => ‘msg.mkit’ });
my $email = $kit->assemble({ user => $user_object });
sendmail($email);
Dear ,
Thank you for being a customer since .
Your account has been due to . Please contact before at or we will be forcedto your lovely wife Tracy’s head.
Cheers,Anonymous
Ugh. Our $user_object was undef. Now we get crap that looks like mail but stinks and makes us look like idiots.
We can plug in a validator pretty easily, though...
Kit Manifest{ "renderer" : "TT", “validator”: “Rx”, "header" : [ { "Subject": "Hello [% user.name %]" }, { "From": "Test Sender <[email protected]>" }, { "To": "[% user.email %]" } ], "alternatives": [ { "type": "text/plain", "path": "body.txt" }, { "type": "text/html", "path": "body.html" } ], "attachments": [ { "path": "demands.rtf" } ]}
rx.json
{ “type”: “//rec”, “required”: { “user”: { “type”: “/perl/obj”, “isa” : “User::Account” } }}
Assembling the Kit
my $kit = Email::MIME::Kit->new({ source => ‘msg.mkit’ });
my $email = $kit->assemble({ user => undef });
undefined value tag: [ “/err/nil” ], data path: [ “user” ], schema path: [ “user” ]at slide 42, line 3
Annoying Crap: Dealt With
Annoying Crap: Dealt With
if user.name is Ævar...
Annoying Crap: Dealt With
if user.name is Ævar...if attachments are binary...
Annoying Crap: Dealt With
if user.name is Ævar...if attachments are binary...text-only (singlepart) mail...
Annoying Crap: Dealt With
if user.name is Ævar...if attachments are binary...text-only (singlepart) mail...HTML with images as attachments...
...so what’s still annoying?
Some Kits...
so, you love mkits and you’re writing them all the time... now you start having this problem...
Some Kits..../body.html./body.txt./logo.jpg./background.jpg./manifest.json
so, you love mkits and you’re writing them all the time... now you start having this problem...
Some Kits..../body.html./body.txt./logo.jpg./background.jpg./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
so, you love mkits and you’re writing them all the time... now you start having this problem...
Some Kits..../body.html./body.txt./logo.jpg./background.jpg./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
so, you love mkits and you’re writing them all the time... now you start having this problem...
Some Kits..../body.html./body.txt./logo.jpg./background.jpg./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
so, you love mkits and you’re writing them all the time... now you start having this problem...
Some Kits..../body.html./body.txt./logo.jpg./background.jpg./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
./body.html
./body.txt
./logo.jpg
./background.jpg
./manifest.json
those files are identical everywhere. blaugh! duplication baaaaaaaaaad
The Kit Reader
The Kit Reader
EMKit gets at its contents with the kit reader
The Kit Reader
EMKit gets at its contents with the kit readernormally, just looks for files in the kit directory
SWAK!
/fs (can be chrooted)/dist/kit (default, too)
...and more...
SWAK!
you can write your own kit reader
/fs (can be chrooted)/dist/kit (default, too)
...and more...
SWAK!
you can write your own kit readerSWAK: Path::Resolver
/fs (can be chrooted)/dist/kit (default, too)
...and more...
SWAK!
you can write your own kit readerSWAK: Path::Resolver
/kit/body.html
/fs (can be chrooted)/dist/kit (default, too)
...and more...
SWAK!
you can write your own kit readerSWAK: Path::Resolver
/kit/body.htmlbody.html
/fs (can be chrooted)/dist/kit (default, too)
...and more...
SWAK!
you can write your own kit readerSWAK: Path::Resolver
/kit/body.htmlbody.html/fs/usr/share/app/body.html
/fs (can be chrooted)/dist/kit (default, too)
...and more...
SWAK in Use
./body.html
./body.txt
./manifest.json
./body.html
./body.txt
./manifest.json
./body.html
./body.txt
./manifest.json
So, we can take those common files and put them in our dist’s shared resources, and reference them with the “/dist/” prefix, which finds stuff in share-dirs.
So, awesome! What’s still annoying?
SWAK in Use
./body.html
./body.txt
./manifest.json
./body.html
./body.txt
./manifest.json
./body.html
./body.txt
./manifest.json
/dist/YourApp/logo.jpg/dist/YourApp/background.jpg
So, we can take those common files and put them in our dist’s shared resources, and reference them with the “/dist/” prefix, which finds stuff in share-dirs.
So, awesome! What’s still annoying?
SWAK in Use
./body.html
./body.txt
./manifest.json
./body.html
./body.txt
./manifest.json
./body.html
./body.txt
./manifest.json
/dist/YourApp/logo.jpg/dist/YourApp/background.jpg
almost certainly, the html and text parts are (a) really close to each other within one kit (b) contain really different parts between kits (c) contain common boilerplate between kits
EMK::Assembler::Markdown
./body.mkdn
./manifest.json
./body.mkdn
./manifest.json
./body.mkdn
./manifest.json
/dist/YourApp/logo.jpg/dist/YourApp/background.jpg
So, let’s fix that, too. We replace the text and html parts with a single Markdown document. We’ll use the Markdown itself for the plaintext part and we’ll turn it into HTML to use in the HTML part.
Of course, this still needs work. We want wrapper stuff and copyright and so on in both parts.
./body.mkdn
./manifest.json
./body.mkdn
./manifest.json
./body.mkdn
./manifest.json
/dist/YourApp/logo.jpg/dist/YourApp/background.jpg/dist/YourApp/wrapper.html/dist/YourApp/wrapper.text
EMK::Assembler::Markdown
Thank you!