HTMLメールを送信する時、クライアントによってはlinkでのcss指定やstyleタグを解釈しない場合があります。その場合、CSSをHTML要素にインライン化しないといけないのですが、今回はこのことについて書いてみます。
CSS::Inlinerを使う
perlにはCSS::Inlinerというモジュールがあって、これを使えばstyleタグの入ったhtmlに対して、CSSインライン化することが出来ます。
例えば以下の様なHTMLがあった場合
<html> <head> <style type="text/css">body { margin: 0px; } #a { padding: 0px; }</style> </head> <body> <p id="a">hoge</p> <p id="b">fuga</p> </body> </html>
以下の様なコードを書けば
my $inliner = CSS::Inliner->new; $inliner->read({ html => $html }); print $inliner->inlinify;
このようにInline化されます。後はこのHTMLを使って送信すればメールクライアントにスタイルを解釈させることが出来ます。
<html> <head></head> <body style="margin: 0px;"> <p id="a" style="padding: 0px;">hoge</p> <p id="b">fuga</p> </body> </html>
CSS::Inlinerを使う場合の問題
通常の場合はCSS::Inlinerを使えば問題ありませんが、問題はメール送信数が多くなってくると、CSSのインライン化がかなりの時間を使ってしまうという事です。
htmlの大きさやCSSの大きさにもよりますが、手元のPCで適当に100通くらいCSSのインライン化をしてみたら、大体6秒くらい時間がかかりました。もしこの速度で実行されたとすると、1000通で1分、10000通で10分と無視できない感じになってきます。
そこで送信前にXslateのテンプレートに対してCSSインライン化をし、その後renderするということを考えます。
Xslateのテンプレートをインライン化する
Xslateのテンプレートをインライン化することにより、10000通送る時でも最初の一回だけインライン化すれば済むことになります。
この時に以下の様なことが問題になります。
- そのままインライン化すると、XslateのタグなどがHTML Entity化されてしまう
- HTML Entity化される対象の文字を少なくするとCSSによってはHTMLが壊れる
そこで今回は一旦Xslate上のタグをHTML Entity化されない文字のプレースホルダーに置き換え、CSSインライン化した後、プレースホルダーをもとに戻すという方法を試してみました*1。
まずxslateのテンプレートとして以下の様なものを使います。
my $template = <<'XSLATE'; <html> <head> <style type="text/css">body { margin: 0px; } #a { padding: 0px; }</style> </head> <body> [% IF 0 %] <p id="a">hoge</p> [% END # IF 0 %] <p id="b">fuga</p> </body> </html> XSLATE
その後以下の様にプレースホルダー変換、インライン化、プレースホルダー逆変換をします。
# [% %]のXslateのsyntaxを[mail_template_compiler_placeholder:1] # のようなプレースホルダーに置き換える my $placeholder_to_syntax = {}; my $placeholder_count = 0; $template =~ s{(\[%.+?%\])}{ my $syntax = $1; my $placeholder = "[mail_template_compiler_placeholder:$placeholder_count]"; $placeholder_to_syntax->{$placeholder} = $syntax; $placeholder_count++; $placeholder; }gse; # CSSインライン化 my $inliner = CSS::Inliner->new; $inliner->read({ html => $template }); $template = $inliner->inlinify; # placeholderをXslateのsyntaxに戻す $template =~ s{(\[mail_template_compiler_placeholder:\d+\])}{ my $placeholder = $1; my $syntax = $placeholder_to_syntax->{$placeholder}; }gse; print $template;
すると以下のようにインライン化されます。
<html> <head> </head> <body style="margin: 0px;"> [% IF 0 %] <p id="a" style="padding: 0px;">hoge</p> [% END # IF 0 %] <p id="b">fuga</p> </body> </html>
あとはこれを使ってXslateでrenderすることで、たくさんのメールを毎回インライン化することなく、HTMLメール送信することが出来ます。