bernhard Posted February 6 Posted February 6 Ever felt like your ProcessWire emails look like they're stuck in 1999? You know the drill - sending emails is super easy with WireMail: $m = new WireMail(); $m->from('foo@bar.com'); $m->to('xxx@yyy.com'); $m->subject('Hello there!'); $m->bodyHTML('<h1>This is great!</h1><p>I am an ugly mail...</p>'); $m->send(); But let's be honest - they look about as pretty as a website built with Microsoft FrontPage! 😅 🪄 Enter the Mail Pimp Hook! Drop this magical hook into your /site/ready.php (or even better Site.module.php), and watch your emails transform from ugly ducklings into beautiful swans: <?php $wire->addHookBefore('WireMail::send', function(HookEvent $event) { // double check that we got a wiremail instance // this also tells the IDE what $mail is (to get IntelliSense) $mail = $event->object; if (!$mail instanceof WireMail) return; // get current mail body $html = $mail->get('bodyHTML'); if (!$html) return; // get email layout markup $layoutFile = wire()->config->paths->templates . 'mails/default.html'; if (!is_file($layoutFile)) return; // replace ##content## with actual mail content $html = str_replace( '##content##', $html, wire()->files->render($layoutFile) ); // write new body to mail $mail->bodyHTML($html); });  The HTML Just create a beautiful MJML template at /site/templates/mails/default.mjml, put ##content## where your email content should go, convert it to HTML and BOOM! 💥 Every email gets automatically wrapped in your gorgeous template. No more CSS wrestling matches, no more "Why does this look different in Outlook?" headaches. Just pure email beauty, automagically! ✨ Now your clients will think you spent days crafting those emails, when in reality, you're sipping coffee while your hook does all the heavy lifting. Work smarter, not harder! 🚀 #ProcessWire #EmailMagic #NoMoreUglyEmails PS: This is the MJML template that I used: <mjml> <mj-head> <mj-attributes> <mj-all font-family="Tahoma" /> <mj-text line-height="140%" /> </mj-attributes> </mj-head> <mj-body background-color="#efefef"> <mj-section background-color="#ffffff" background-repeat="repeat" padding-bottom="30px" padding-top="30px" text-align="center" > <mj-column> <mj-image align="center" padding="25px" src="xxx" target="_blank" width="200px" alt="Logo" ></mj-image> <mj-text>##content##</mj-text> </mj-column> </mj-section> <mj-section> <mj-column> <mj-text font-size="10px" color="#a0a0a0" align="center" > powered by <a href="https://www.baumrock.com/" style="color: #158f66" >baumrock.com</a > </mj-text> </mj-column> </mj-section> </mj-body> </mjml> VSCode has an extension to get a live preview and export MJML to HTML: And here are some other free templates: https://mjml.io/templates I use https://www.base64-image.de/ to add the logo to my mail template as src="data:image/jpeg;base64,/9j/4QAWRXhpZgAATU0AKgAAAA..." to avoid headaches with image paths, remote assets blocking etc. 14 6
Jonathan Lahijani Posted February 6 Posted February 6 Nice. Just wanted to mention Cerberus as an alternative to MJML if you want to use email-friendly HTML templates directly. 11 1
Ellyot_Chase Posted February 25 Posted February 25 Wow, really helpfull @bernhard! Thanks. Up until now i had an output buffer and included a template file, or rendered a page before my wiremail call to get an instance. But this will make things way cleaner. Â Â Â Â 1
Cybermano Posted March 17 Posted March 17 Thanks @bernhard in past I have used Cerberus as mentioned by @Jonathan Lahijani, but MJML is very awesome; expecially for the VScode extension with the html compiler. and preview. I think the template could be used directly in the wire mail construct, (without the ready hook): // ... $m->bodyHTML(wire()->files->render($layoutFile)); // ... Or do you think this is not a good practice? Now I'm testing it dynamically with "placeholders" : // get template $bodyHTML = wire()->files->render($layoutFile); // let's say we have 2 placeholders into the html template: one for user name as [[userName]] and one page body content as [[pageBody]] $bodyHTML = str_replace( ["[[userName]]","[[pageBody]]"], [ucfirst(wire('user')->name),$page->body], $bodyHTML, ); // ... $m->bodyHTML($bodyHTML); // ... This works for me: it has great potential. Many thanks again for sharing this solution.
bernhard Posted March 17 Author Posted March 17 Hey @Cybermano glad it was helpful! 4 hours ago, Cybermano said: // ... $m->bodyHTML(wire()->files->render($layoutFile)); // ... Or do you think this is not a good practice? That's what I've been using before I had the idea of hooking into WireMail::send. Everything in life has pro's and con's... So I'd not say it's a bad practise. But what I like about the solution is that it fits my credo "simple things should be simple". Sending a beautiful thing should be simple. And what you show might look QUITE simple it's not as simple as my version. If you only have one mail it won't matter or your version would even be easier (because you see everything that is going on). But if you are sending mails from different places in different occasions (eg on registration, on a new post, on a schedule, etc...) then my version keeps your code DRY (don't repeat yourself) whereas your version either tempts the dev to copy and paste those lines of code or be too lazy and just send out an ugly 90s style email. Both is not ideal in my opinion and investing a little more time to setup the hook will be beneficial in the long run. Also, while it's just a few lines of code, when your codebase grows every line of code that is not instantly and easily understandable matters. Your brain has to read the code, understand it, interpret it and remember it. With your str_replace that might be QUITE easy, but things add up and suddenly it might make the difference between easy to read code and a bloat of spaghetti code. It's about building a habit. Another problem with your solution is that if you copy&paste this to 3 spots, for example, and later you add another replacement tag, you have to do a search&replace and you might miss one spot and you introduce a bug. If you have it on a central place this can not happen. On the other hand if you want to render different files or you want different tags for different emails your approach might be the better one. And for someone not familiar with your codebase it might be easier to understand your version vs. the hook, because only seeing the $mail->send() somewhat hides how the mail gets sent with nice HTML and your version makes that obvious. 1
Cybermano Posted March 17 Posted March 17 Hi @bernhard, thanks for your explanations. I will made my best trying to follow those suggestions. 1
monollonom Posted March 17 Posted March 17 @Cybermano you could also use this module I made https://processwire.com/modules/page-mjml-to-html/, though you lose the ability to see your changes live in VSCode. Pros and cons like @bernhard says 🙂 2 1
Cybermano Posted March 17 Posted March 17 Thanks @monollonom, it looks very interesting. I will surely give a look and try.
PWaddict Posted July 24 Posted July 24 Thank you @bernhard for this! What if I have a LazyCron hook to send emails about expirations and want to display a call to action button on those? Should I create the button on a .mjml file then convert it to html and copy ONLY the HTML part of the button and paste it on BodyHTML of the LazyCron hook or there is a better way?
bernhard Posted July 24 Author Posted July 24 I'd just add the button as regular html that you then place in ##content## in your mjml file. That way you don't have to recreate the .html from the .mjml - but it depends. If you need complex content like a responsive grid then mjml might be the better choice. 1
PWaddict Posted July 24 Posted July 24 Btw, if there are modules or hooks that send only plain text emails like for example some notifications on LoginRegisterPro with the following hook you can force HTML on all outgoing emails and forget about those ugly emails 😎 /** * Force HTML on all outgoing emails * */ $wire->addHookBefore('WireMail::send', function(HookEvent $event) { $wireMail = $event->object; if($wireMail->body && !$wireMail->bodyHTML) { $forceHTML = nl2br($wireMail->body); // nl2br — Inserts HTML line breaks before all newlines in a string $wireMail->bodyHTML($forceHTML); } }); I guess this can get combined with your hook but for now I'm using it as separate.
bernhard Posted July 24 Author Posted July 24 // get current mail body $html = $mail->get('bodyHTML'); if (!$html) $html = nl2br($mail->get('body')); // <--------- add this line if (!$html) return; Â 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now