crossvertise / ActionMailerNext

Fork of ActionMailer.Net, A easy to use library to generate emails with razor views
MIT License
42 stars 24 forks source link

CSS Inlining #3

Open agrigg opened 10 years ago

agrigg commented 10 years ago

It would be great to be able to have the ability to automatically inline css styles as part of sending out an email. I would be open to helping develop this functionality if someone could point me in the right direction for where the best place to hook that in would be.

AndrewHK commented 10 years ago

Seems like an interesting suggestion. I'll do a research about to see what can be done and I will get back to you :)

agrigg commented 10 years ago

Cool, looks like there is already a .NET project, PreMailer.Net that can do CSS inlining. Seems like we could just add an option to enable CSS inlining then process the resulting html through PreMailer.Net before assigning it to the email body.

message.Body = PreMailer.Net.PreMailer.MoveCssInline(message.Body).Html;
nickalbrecht commented 9 years ago

I'm actually already using PreMailer.Net to do this in conjunction with ActionMailer.Net (and now ActionMailerNext). I'm using the MVC implementation, but to to get this setup, simply add this to your class that inherits from MailerBase.

protected override void OnMailSending(MailSendingContext context)
{

    var alternateview = context.Mail.AlternateViews[0];

    //Only attempt to process styles if the email's content type is HTML. If it's plain text, processing the contents will insert a wrapping <html> tag with <head> and <body> elements as well which will show as plain text.
    if (alternateview.ContentType.MediaType.Contains("html"))
    {
        //Convert original email contents stream to a string
        string html;
        using (var memoryStream = new MemoryStream())
        {
            ((MemoryStream)alternateview.ContentStream).WriteTo(memoryStream);
            html = Encoding.UTF8.GetString(memoryStream.ToArray());
        }

        //Use PreMailer to parse the contents and convert <style> contents to inline style="" attributes
        var inlineResult = PreMailer.Net.PreMailer.MoveCssInline(html, true);
        //see inlineResult.Warnings for problems parsing
        html = inlineResult.Html;

        //Replace the original email contents with the newly modified contents
        context.Mail.AlternateViews.Clear();
        context.Mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, Encoding.UTF8, alternateview.ContentType.MediaType));
    }
    base.OnMailSending(context);
}   
agrigg commented 9 years ago

Nice, thanks @yarx! I'll try that out.

nickalbrecht commented 9 years ago

Oh, and just a quick heads up, I found a bug with sending multiple emails in my code that I had to add correction for, in my MemoryStream logic in the using block. The position on the alternateview.ContentStream doesn't get reset to 0 for some reason when I go to send a second email on the same request. So if you have already copied what I had above, you may want to grab that one line's change that changed it from using the Stream.CopyTo() method to instead using MemoryStream.WriteTo()

UPDATE: Actually it seems there is a more systemic change somewhere in the ActionMailerNext that is causing problems when I try to send multiple emails using the same controller... not sure what it is yet.

UPDATE: Yeah, I am not sure what it is, but ActionMailerNext doesn't appear to support sending multiple emails, each with their own view, model, attachments, etc. The source looks fine but when I try to send multiple emails, they are all just copies of the first one I sent. I'm rolling back to ActionMailer.Net for now. I don't have time to debug ActionMAilerNext right now. Either way, the code I have above for using PreMailer to inline the CSS works fine.