i

107
the 2009 PEP talk!!!

description

An overview of Email::Sender and Email::MIME::Kit, new Perl libraries to help ease the pain of dealing with email.

Transcript of i

Page 1: i

the 2009 PEP talk!!!

Page 2: i

...I work there!

Page 3: i

YAPC::NA 2006

Page 4: i

PEP: Thoughts from PoboxYAPC::NA 2006

Page 5: i

YAPC::NA 2007

Page 6: i

How I Learned To Stop Worrying and Love Email

YAPC::NA 2007

Page 7: i

YAPC::NA 2008

Page 8: i

EmailYAPC::NA 2008

hates the living!

Page 9: i

What’s the best way to deal with horrible code?

Page 10: i

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...

Page 11: i

rjbs<3

emailthe 2009 PEP talk!!!

Page 12: i

sending email

Page 13: i

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

Page 14: i

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

Page 15: i

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.

Page 16: i

this is probably about about as good as a number of them.

Page 17: i

system(“sendmail @opts < $tempfile”) && die sprintf “sendmail died: %s”, errstr($?);

this is probably about about as good as a number of them.

Page 18: i

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

Page 19: i
Page 20: i

Email::Sender

Page 21: i

Email::Sender

Page 22: i

Email::Sender

Email-Sender uses Møøse

Page 23: i

Email::Sender

Email-Sender uses MøøseEmail::Sender is a role

Page 24: i

Email::Sender

Email-Sender uses MøøseEmail::Sender is a roleyou use Email::Sender::Simple

Page 25: i

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.

Page 26: i

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.

Page 27: i

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:

Page 28: i

Transports

SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.

Page 29: i

TransportsSendmail

SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.

Page 30: i

TransportsSendmail

SMTP

SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.

Page 31: i

TransportsSendmail

SMTP

Persist. SMTP

SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.

Page 32: i

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.

Page 33: i

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.

Page 34: i

TransportsSendmail

SMTP

Persist. SMTP

Maildir, Mbox

DevNull

Print

SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.

Page 35: i

TransportsSendmail

SMTP

Persist. SMTP

Maildir, Mbox

DevNull

Print

Test

SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.

Page 36: i

TransportsSendmail

SMTP

Persist. SMTP

Maildir, Mbox

DevNull

Print

Test

SQLite

SQLite is especially handy because you can dump all mail from a forking program into an easy-to-test database.

Page 37: i

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?

Page 38: i

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

Page 39: i

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

Page 40: i

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

Page 41: i

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

Page 42: i

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

Page 43: i

Other Random Senderisms

Page 44: i

Other Random Senderisms

structured failure

Page 45: i

Other Random Senderisms

structured failurepartial successes

Page 46: i

making email messages

Page 47: i

Email::Simple

Page 48: i

Email::MIME

Page 49: i

these are pretty low level

Page 50: i

you probably want to send only a few messages

(but with differences)

Page 51: i

MIME Crap to Deal With

Page 52: i

MIME Crap to Deal With

header encoding

Page 53: i

MIME Crap to Deal With

header encodingcontent encoding

Page 54: i

MIME Crap to Deal With

header encodingcontent encodingbuilding html & plain parts

Page 55: i

MIME Crap to Deal With

header encodingcontent encodingbuilding html & plain partsattaching files

Page 56: i

Other Crap to Deal With

Page 57: i

Other Crap to Deal With

templated documents

Page 58: i

Other Crap to Deal With

templated documentsvalidate parameters

Page 59: i

Other Crap to Deal With

templated documentsvalidate parametersreusable hunks of content

Page 60: i
Page 61: i

hate!

Page 62: i

Existing Solutions

Page 63: i

Existing Solutionsnone?

Page 64: i

Existing Solutionsnone?horrible hacks

Page 65: i

Existing Solutionsnone?horrible hacks

make a template in TT

Page 66: i

Existing Solutionsnone?horrible hacks

make a template in TTrender to html

Page 67: i

Existing Solutionsnone?horrible hacks

make a template in TTrender to htmlhtml-to-text

Page 68: i

Existing Solutionsnone?horrible hacks

make a template in TTrender to htmlhtml-to-texthope you guess right at headers

Page 69: i
Page 70: i

Email::MIME::Kit

Page 71: i

Email::MIME::Kit

Page 72: i

Email::MIME::Kit

a bunch of files

Page 73: i

Email::MIME::Kit

a bunch of fileswith instructions on how to assemble them

Page 74: i

Email::MIME::Kit

a bunch of fileswith instructions on how to assemble themand some other data

Page 75: i

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" } ]}

Page 76: i

Assembling the Kit

my $kit = Email::MIME::Kit->new({ source => ‘msg.mkit’ });

my $email = $kit->assemble({ user => $user_object });

sendmail($email);

Page 77: i

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...

Page 78: i

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" } ]}

Page 79: i

rx.json

{ “type”: “//rec”, “required”: { “user”: { “type”: “/perl/obj”, “isa” : “User::Account” } }}

Page 80: i

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

Page 81: i

Annoying Crap: Dealt With

Page 82: i

Annoying Crap: Dealt With

if user.name is Ævar...

Page 83: i

Annoying Crap: Dealt With

if user.name is Ævar...if attachments are binary...

Page 84: i

Annoying Crap: Dealt With

if user.name is Ævar...if attachments are binary...text-only (singlepart) mail...

Page 85: i

Annoying Crap: Dealt With

if user.name is Ævar...if attachments are binary...text-only (singlepart) mail...HTML with images as attachments...

Page 86: i

...so what’s still annoying?

Page 87: i

Some Kits...

so, you love mkits and you’re writing them all the time... now you start having this problem...

Page 88: i

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...

Page 89: i

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...

Page 90: i

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...

Page 91: i

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...

Page 92: i

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

Page 93: i

The Kit Reader

Page 94: i

The Kit Reader

EMKit gets at its contents with the kit reader

Page 95: i

The Kit Reader

EMKit gets at its contents with the kit readernormally, just looks for files in the kit directory

Page 96: i

SWAK!

/fs (can be chrooted)/dist/kit (default, too)

...and more...

Page 97: i

SWAK!

you can write your own kit reader

/fs (can be chrooted)/dist/kit (default, too)

...and more...

Page 98: i

SWAK!

you can write your own kit readerSWAK: Path::Resolver

/fs (can be chrooted)/dist/kit (default, too)

...and more...

Page 99: i

SWAK!

you can write your own kit readerSWAK: Path::Resolver

/kit/body.html

/fs (can be chrooted)/dist/kit (default, too)

...and more...

Page 100: i

SWAK!

you can write your own kit readerSWAK: Path::Resolver

/kit/body.htmlbody.html

/fs (can be chrooted)/dist/kit (default, too)

...and more...

Page 101: i

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...

Page 102: i

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?

Page 103: i

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?

Page 104: i

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

Page 105: i

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.

Page 106: i

./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

Page 107: i

Thank you!