<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/email/</id>
  <link href="https://www.endpointdev.com/blog/tags/email/"/>
  <link href="https://www.endpointdev.com/blog/tags/email/" rel="self"/>
  <updated>2026-03-13T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Why Your AI Extractor Fails on .msg Emails (and How to Fix Decoding)</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2026/03/why-ai-extractor-fails-on-msg-emails-and-how-to-fix-decoding/"/>
      <id>https://www.endpointdev.com/blog/2026/03/why-ai-extractor-fails-on-msg-emails-and-how-to-fix-decoding/</id>
      <published>2026-03-13T00:00:00+00:00</published>
      <author>
        <name>Edgar Mlowe</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2026/03/why-ai-extractor-fails-on-msg-emails-and-how-to-fix-decoding/antenna-and-sky.webp&#34; alt=&#34;Against a blue sky with wispy white clouds, an old directional antenna points to the left of the camera atop a brick fireplace&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2025. --&gt;
&lt;p&gt;I want to share a debugging lesson that saved me from tuning the wrong layer in an AI extraction pipeline.&lt;/p&gt;
&lt;p&gt;It started with a familiar symptom: extraction output looked inconsistent. Some rows were fine, but some had extra characters, especially accents. My first instinct was the same one most of us have: maybe the model needs prompt tuning.&lt;/p&gt;
&lt;p&gt;It turned out not to be a model problem. The root cause was upstream data integrity: decoding &lt;code&gt;.msg&lt;/code&gt; email HTML with the wrong charset.&lt;/p&gt;
&lt;h3 id=&#34;the-pattern-that-gives-it-away&#34;&gt;The pattern that gives it away&lt;/h3&gt;
&lt;p&gt;If you see this mix, think decoding first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;output is mostly correct, but certain names and addresses look garbled&lt;/li&gt;
&lt;li&gt;problems appear only for some senders or date ranges&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.eml&lt;/code&gt; looks stable, but &lt;code&gt;.msg&lt;/code&gt; is inconsistent&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A classic sign looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;expected: &lt;code&gt;Müller&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;corrupted: &lt;code&gt;MÃ¼ller&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the time your extractor sees that text, the meaning is already damaged.&lt;/p&gt;
&lt;h3 id=&#34;why-msg-bites-harder-than-eml&#34;&gt;Why &lt;code&gt;.msg&lt;/code&gt; bites harder than &lt;code&gt;.eml&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Quick definitions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.eml&lt;/code&gt; is the standard MIME email format and usually includes charset metadata per part.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.msg&lt;/code&gt; is an Outlook container format (MAPI), where body bytes and encoding hints can be stored separately.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That difference matters.&lt;/p&gt;
&lt;p&gt;If your code assumes UTF-8 for &lt;code&gt;.msg&lt;/code&gt; HTML bytes, non-UTF messages can decode into garbage. Then downstream steps (HTML-to-PDF, OCR, LLM extraction, post-processing) just preserve and propagate bad text.&lt;/p&gt;
&lt;h3 id=&#34;the-fix-strict-explicit-controlled&#34;&gt;The fix: strict, explicit, controlled&lt;/h3&gt;
&lt;p&gt;You do not need a big rewrite. A small decode policy change can remove a whole class of silent failures. For &lt;code&gt;.msg&lt;/code&gt; HTML bytes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the encoding hint from message metadata.&lt;/li&gt;
&lt;li&gt;Map that hint to a decoder codec.&lt;/li&gt;
&lt;li&gt;Decode in strict mode.&lt;/li&gt;
&lt;li&gt;If needed, use one controlled strict fallback.&lt;/li&gt;
&lt;li&gt;If decode still fails, fail loud.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Minimal example in Python:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;extract_msg.encoding&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; lookupCodePage
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PR_INTERNET_CODEPAGE = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;3FDE0003&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;decode_msg_html_bytes&lt;/span&gt;(html_bytes: &lt;span style=&#34;color:#038&#34;&gt;bytes&lt;/span&gt;, message) -&amp;gt; &lt;span style=&#34;color:#038&#34;&gt;str&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    codepage_id = message.getPropertyVal(PR_INTERNET_CODEPAGE)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    codec = (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        lookupCodePage(codepage_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;isinstance&lt;/span&gt;(codepage_id, &lt;span style=&#34;color:#038&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#080&#34;&gt;and&lt;/span&gt; codepage_id &amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; html_bytes.decode(codec, errors=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;strict&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;except&lt;/span&gt; (&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;LookupError&lt;/span&gt;, &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;UnicodeDecodeError&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; html_bytes.decode(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;, errors=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;strict&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;why-i-prefer-fail-loud-here&#34;&gt;Why I prefer fail-loud here&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;errors=&amp;quot;replace&amp;quot;&lt;/code&gt; keeps jobs moving, but it can hide real data corruption.&lt;/p&gt;
&lt;p&gt;For low-stakes preview features, that may be acceptable.
For transactional extraction (orders, invoices, legal, shipping), silent corruption is usually worse than an explicit failure.&lt;/p&gt;
&lt;p&gt;Use this decision rule:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Use case&lt;/th&gt;
          &lt;th&gt;Policy&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Preview/search UX&lt;/td&gt;
          &lt;td&gt;Best-effort can be acceptable with clear flags&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Transactional extraction&lt;/td&gt;
          &lt;td&gt;Strict decode + fail loud&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Mixed systems&lt;/td&gt;
          &lt;td&gt;Strict on extraction path, best-effort on preview path&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;how-to-roll-this-out-safely&#34;&gt;How to roll this out safely&lt;/h3&gt;
&lt;p&gt;Keep blast radius low:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Change only the failing decode path first.&lt;/li&gt;
&lt;li&gt;Validate on a representative dataset, not one sample file.&lt;/li&gt;
&lt;li&gt;Leave unrelated paths untouched until evidence says otherwise.&lt;/li&gt;
&lt;li&gt;Expand strict policy incrementally.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This gives reliability without destabilizing the rest of the ingestion stack.&lt;/p&gt;
&lt;h3 id=&#34;observability-that-makes-this-easier-next-time&#34;&gt;Observability that makes this easier next time&lt;/h3&gt;
&lt;p&gt;Log these fields per message:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Source file&lt;/li&gt;
&lt;li&gt;Content source used (HTML or plain text)&lt;/li&gt;
&lt;li&gt;Whether encoding hint was found&lt;/li&gt;
&lt;li&gt;Selected codec&lt;/li&gt;
&lt;li&gt;Whether fallback was used&lt;/li&gt;
&lt;li&gt;Result of decoding (success, fallback, manual review, fail)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this, “random extraction quality” turns into a clear ingestion signal.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Using Razor templates to render HTML emails in ASP.NET Core</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/08/using-razor-templates-to-render-html-emails-in-asp-net/"/>
      <id>https://www.endpointdev.com/blog/2025/08/using-razor-templates-to-render-html-emails-in-asp-net/</id>
      <published>2025-08-15T00:00:00+00:00</published>
      <author>
        <name>Kevin Campusano</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/08/using-razor-templates-to-render-html-emails-in-asp-net/gothic-church-low-angle.webp&#34; alt=&#34;The side of a gothic church from a very low angle. The ornate spires and gargoyles loom over the viewer, with dark stained glass beneath, sectioned by square stone pillars.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2024. --&gt;
&lt;p&gt;A while ago &lt;a href=&#34;/blog/2024/04/using-razor-templates-to-render-emails-dotnet/&#34;&gt;I blogged&lt;/a&gt; about using Razor templates to render HTML emails in &lt;a href=&#34;https://dotnet.microsoft.com/en-us/&#34;&gt;.NET&lt;/a&gt;. The method that I discussed there worked, but it was very verbose. Since then, &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8/overview&#34;&gt;.NET 8 has released&lt;/a&gt;, and with it came a simpler way of doing this. In this post we&amp;rsquo;ll explore how to use these new features to render HTML emails.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can find all the code in this post on &lt;a href=&#34;https://github.com/megakevin/end-point-blog-razor-emails&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;the-plan&#34;&gt;The plan&lt;/h3&gt;
&lt;p&gt;Similar to the original article, the objective is simple: Sending emails from an ASP.NET Core app, and having the contents of those emails be rendered fromfrom  Razor templates. To that end, we need four pieces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A class for sending emails.&lt;/li&gt;
&lt;li&gt;A class for rendering Razor templates into strings.&lt;/li&gt;
&lt;li&gt;A Razor template.&lt;/li&gt;
&lt;li&gt;A class that puts it all together. That is, takes in parameters, renders the email, and sends it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;step-1-sending-emails-with-the-mailkit-nuget-package&#34;&gt;Step 1: Sending emails with the MailKit NuGet package&lt;/h3&gt;
&lt;p&gt;With the help of the MailKit NuGet package, sending emails in .NET is easy. Let&amp;rsquo;s install it with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet add package MailKit --version 4.13.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We also need some configuration in the &lt;code&gt;appsettings.json&lt;/code&gt; file, to define the settings needed to establish a connection with an SMTP server:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ./appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;MailSettings&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Server&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;YOUR_SMTP_SERVER&amp;gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;587&lt;/span&gt;, &lt;span style=&#34;color:#888&#34;&gt;// 25 or 465 or 587 or 2525
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;SenderName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;RazorEmails&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;SenderEmail&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;test@razoremails.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;UserName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;YOUR_SMTP_SERVER_USER_NAME&amp;gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Password&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;lt;YOUR_SMTP_SERVER_USER_PASSWORD&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And here&amp;rsquo;s a simple &lt;code&gt;Mailer&lt;/code&gt; class that uses the &lt;code&gt;MailKit&lt;/code&gt; library and the configuration above to send emails:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ./Mailers/Mailer.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MailKit.Net.Smtp&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MimeKit&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorEmails.Mailers&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MailData&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; To { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; ToName { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Subject { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Body { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mailer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; ILogger&amp;lt;Mailer&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; IConfiguration _config;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; Mailer(IConfiguration config, ILogger&amp;lt;Mailer&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _config = config;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Not much to see here, just a method for sending emails using MailKit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// Docs are here: https://github.com/jstedfast/MailKit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;bool&lt;/span&gt;&amp;gt; SendMailAsync(MailData mailData)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; emailMessage = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; MimeMessage();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.From.Add(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; MailboxAddress(MailSenderName, MailSenderEmail));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.To.Add(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; MailboxAddress(mailData.ToName, mailData.To));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.Subject = mailData.Subject;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; emailBodyBuilder = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyBuilder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                TextBody = mailData.Body,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HtmlBody = mailData.Body
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.Body = emailBodyBuilder.ToMessageBody();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;var&lt;/span&gt; smtpClient = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; SmtpClient();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; smtpClient.ConnectAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                MailServer,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                MailPort,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                MailKit.Security.SecureSocketOptions.StartTls
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; smtpClient.AuthenticateAsync(MailUserName, MailPassword);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; smtpClient.SendAsync(emailMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; smtpClient.DisconnectAsync(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;catch&lt;/span&gt; (Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _logger.LogError(ex, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Failed to send email to {To}&amp;#34;&lt;/span&gt;, mailData.To);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; MailSenderName =&amp;gt; _config[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings:SenderName&amp;#34;&lt;/span&gt;]!;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; MailSenderEmail =&amp;gt; _config[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings:SenderEmail&amp;#34;&lt;/span&gt;]!;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; MailServer =&amp;gt; _config[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings:Server&amp;#34;&lt;/span&gt;]!;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;int&lt;/span&gt; MailPort =&amp;gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;int&lt;/span&gt;.Parse(_config[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings:Port&amp;#34;&lt;/span&gt;]!);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; MailUserName =&amp;gt; _config[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings:UserName&amp;#34;&lt;/span&gt;]!;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; MailPassword =&amp;gt; _config[&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings:Password&amp;#34;&lt;/span&gt;]!;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;step-2-rendering-razor-templates-into-strings&#34;&gt;Step 2: Rendering Razor templates into strings&lt;/h3&gt;
&lt;p&gt;Of course, we also need a way of rendering Razor templates. As mentioned in the beginning, .NET 8 made this easy. There&amp;rsquo;s &lt;a href=&#34;https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-components-outside-of-aspnetcore?view=aspnetcore-9.0&#34;&gt;a page in the official documentation&lt;/a&gt; and &lt;a href=&#34;https://andrewlock.net/exploring-the-dotnet-8-preview-rendering-blazor-components-to-a-string/&#34;&gt;community blog posts&lt;/a&gt; talking about it. For our purposes, here&amp;rsquo;s a class that does it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ./Rendering/RazorViewRenderer.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Components&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Components.Web&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorEmails.Rendering&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorViewRenderer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; IServiceProvider _serviceProvider;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; ILoggerFactory _loggerFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; RazorViewRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _serviceProvider = serviceProvider;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _loggerFactory = loggerFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt;&amp;gt; Render&amp;lt;TView, TViewModel&amp;gt;(TViewModel model) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;where&lt;/span&gt; TView : IComponent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;var&lt;/span&gt; htmlRenderer = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; HtmlRenderer(_serviceProvider, _loggerFactory);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; html = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; htmlRenderer.Dispatcher.InvokeAsync(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; output = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; htmlRenderer.RenderComponentAsync&amp;lt;TView&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ParameterView.FromDictionary(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; Dictionary&amp;lt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;object?&lt;/span&gt;&amp;gt; { { &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;, model } }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; output.ToHtmlString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; html;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Render&lt;/code&gt; method is where the magic happens. We&amp;rsquo;ll see how to use it soon, but for now, it&amp;rsquo;s interesting to look at the generic type parameters.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TView&lt;/code&gt; represents the strongly-typed Razor template that will be rendered. Technically, the &amp;ldquo;template&amp;rdquo; is actually a &lt;a href=&#34;https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-9.0&#34;&gt;Razor component&lt;/a&gt;, as we&amp;rsquo;ll see later. That&amp;rsquo;s why we use &lt;code&gt;where TView : IComponent&lt;/code&gt; as a &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint&#34;&gt;generic type constraint&lt;/a&gt;. With it, we&amp;rsquo;re specifying that the given &lt;code&gt;TView&lt;/code&gt; must inherit from &lt;code&gt;Microsoft.AspNetCore.Components.IComponent&lt;/code&gt;, which is the base class of Razor components.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TViewModel&lt;/code&gt;, on the other hand, represents the type of the data structure that will be passed to the template as a parameter. It will contain data that the template will use to render itself.&lt;/p&gt;
&lt;h3 id=&#34;step-3-defining-the-email-templates&#34;&gt;Step 3: Defining the email templates&lt;/h3&gt;
&lt;p&gt;Okay, now we need to define our templates, along with the vehicles to pass data to them. These will be simple &lt;a href=&#34;https://en.wikipedia.org/wiki/Data_transfer_object&#34;&gt;DTOs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When sending emails from web applications, it is often useful to define a layout that all emails use to keep their styling consistent. Using Razor components, such a layout could look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- ./Rendering/Views/MainLayout.razor --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@inherits LayoutComponentBase
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;lang&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;content&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;http-equiv&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;content&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;text/html; charset=utf-8&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  @Body
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is just a basic shell for an HTML document. Notice two interesting aspects of it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;@inherits LayoutComponentBase&lt;/code&gt; declares that our layout inherits from the &lt;code&gt;LayoutComponentBase&lt;/code&gt;. This is necessary for the layout to actually act as a layout.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Body&lt;/code&gt; determines where the contents of the components that use this layout will be rendered. In this case, we put it inside the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; HTML tag, which seems appropriate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now to define the template for a basic email that uses this layout:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- ./Rendering/Views/MessageEmail.razor --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@using RazorEmails.Rendering.ViewModels
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;LayoutView&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;Layout&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;@typeof(MainLayout)&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Hello @Model.Greeting.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  We have a message for you: @Model.Message
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  Message sent at @DateTime.Now.ToString().
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;LayoutView&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@code {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  [Parameter]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  public MessageEmailViewModel Model { get; set; } = default!;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a very straightforward Razor component that takes in a &lt;code&gt;MessageEmailViewModel&lt;/code&gt; as a parameter and renders a few lines of text using the data coming in the parameter. By virtue of its file name, this component can be referenced in code by the &lt;code&gt;MessageEmail&lt;/code&gt; class name. It uses tried and true Razor syntax, so little of this should be surprising if you&amp;rsquo;ve worked with Razor before.&lt;/p&gt;
&lt;p&gt;One thing to notice is how we&amp;rsquo;re explicitly declaring the &lt;code&gt;LayoutView&lt;/code&gt; element, pointing to the &lt;code&gt;MainLayout&lt;/code&gt; that we wrote. This is how we tell the renderer to use our layout when rendering this component. &lt;code&gt;MainLayout&lt;/code&gt; exists because that&amp;rsquo;s what we named the file that contains the layout.&lt;/p&gt;
&lt;p&gt;Like I mentioned before, &lt;code&gt;MessageEmailViewModel&lt;/code&gt; is a simple DTO that serves to pass some data to the template. It&amp;rsquo;s not very exciting, but this is what it looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ./Rendering/ViewModels/MessageEmailViewModel.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorEmails.Rendering.ViewModels&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MessageEmailViewModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Greeting { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Message { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;step-4-putting-it-all-together-the-class-that-sends-the-email&#34;&gt;Step 4: Putting it all together: the class that sends the email&lt;/h3&gt;
&lt;p&gt;And finally, here we have a class that puts these separate elements to work to send an email:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// ./Mailers/MessageMailer.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorEmails.Rendering&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorEmails.Rendering.ViewModels&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorEmails.Rendering.Views&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorEmails.Mailers&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MessageMailer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; Mailer _mailer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; RazorViewRenderer _razorViewRenderer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; MessageMailer(Mailer mailer, RazorViewRenderer razorViewRenderer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _mailer = mailer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _razorViewRenderer = razorViewRenderer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task SendAsync(&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; to, &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; toName, &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; greeting, &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; body = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _razorViewRenderer.Render&amp;lt;MessageEmail, MessageEmailViewModel&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; MessageEmailViewModel() { Greeting = greeting, Message = message }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _mailer.SendMailAsync(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            To = to,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ToName = toName,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Subject = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;$&amp;#34;{greeting}, we have a message for you.&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Body = body
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;SendAsync&lt;/code&gt; method sends the email. It takes a series of parameters and uses them to construct a &lt;code&gt;MessageEmailViewModel&lt;/code&gt; object which is passed to the &lt;code&gt;RazorViewRenderer&lt;/code&gt;&amp;rsquo;s &lt;code&gt;Render&lt;/code&gt; method. It also specifies &lt;code&gt;MessageEmail&lt;/code&gt; as the Razor component to render. &lt;code&gt;RazorViewRenderer&lt;/code&gt; returns a string which is then sent to &lt;code&gt;Mailer&lt;/code&gt;&amp;rsquo;s &lt;code&gt;SendMailAsync&lt;/code&gt; method as the &lt;code&gt;Body&lt;/code&gt; parameter, along with other parameters. The &lt;code&gt;Mailer&lt;/code&gt; then uses these parameters to construct an email and send it.&lt;/p&gt;
&lt;p&gt;To send emails, this class can be used like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// Imagine we&amp;#39;re running this in an ASP.NET Core app and getting an instance of MessageMailer via DI...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; MessageMailer _messageMailer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;//... then, within some method, we can do:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _messageMailer.SendAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;recipient@example.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Recipient Name&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Mr. Recipient&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Have a good day.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Simple and clean.&lt;/p&gt;
&lt;p&gt;All right! That&amp;rsquo;s it for now. In this article we saw how to leverage new .NET 8 features for rendering Razor components into strings. We implemented an email sending capability based on that and the MailKit NuGet package. It was a nice way of revisiting an old topic, now made easier thanks to the latest updates from .NET.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Using Razor templates to render HTML emails in .NET</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/04/using-razor-templates-to-render-emails-dotnet/"/>
      <id>https://www.endpointdev.com/blog/2024/04/using-razor-templates-to-render-emails-dotnet/</id>
      <published>2024-04-30T00:00:00+00:00</published>
      <author>
        <name>Kevin Campusano</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/04/using-razor-templates-to-render-emails-dotnet/power-lines.webp&#34; alt=&#34;Several power lines of varying sizes appear as black lines covering the center third of the image, rising from the bottom left to the top right across a pure blue sky. In the bottom left, on one line sits a bird, far enough away that it is a small blob. In the top right, centered between two of the lines, is the bright gibbous moon.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2024. --&gt;
&lt;blockquote&gt;
&lt;p&gt;Update August 2025: .NET 8 has simplified the process of rendering HTML emails. You can read an &lt;a href=&#34;/blog/2025/08/using-razor-templates-to-render-html-emails-in-asp-net/&#34;&gt;updated post on this blog!&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;When it comes to sending emails, Ruby on Rails has an excellent solution in the form of &lt;a href=&#34;https://guides.rubyonrails.org/action_mailer_basics.html&#34;&gt;Action Mailer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The basic idea is that you can define email templates using &lt;a href=&#34;https://github.com/ruby/erb&#34;&gt;ERB&lt;/a&gt; files. This is the same templating engine/​language used for normal web application views. Then, &lt;a href=&#34;https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration&#34;&gt;application-level SMTP settings are configured&lt;/a&gt; for email delivery. Finally, a &amp;ldquo;&lt;a href=&#34;https://guides.rubyonrails.org/action_mailer_basics.html#sending-emails&#34;&gt;Mailer&lt;/a&gt;&amp;rdquo; class can be developed that leverages the templates and the underlying email sending mechanism to send emails.&lt;/p&gt;
&lt;p&gt;In Rails, all this comes right out the box. Setup is minimal, so this approach is a huge time saver for a task that&amp;rsquo;s very common in web applications.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://dotnet.microsoft.com/en-us/apps/aspnet&#34;&gt;ASP.NET Core&lt;/a&gt; (or .NET in general), we don&amp;rsquo;t have such a convenient, built-in solution. However, it is possible to implement our own using the framework&amp;rsquo;s features.&lt;/p&gt;
&lt;p&gt;In this article, I&amp;rsquo;m going to explain step by step what I did in a recent .NET project to develop functionality similar to what Action Mailer provides.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Throughout this article, I will be using a demo Web API application for code examples. If you&amp;rsquo;d like to see what the final implementation looks like, &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-8-demo&#34;&gt;you can find all the code on GitHub&lt;/a&gt;. The API is about calculating quotes for used vehicles. Using the process described here, I added the feature to send emails when new quotes are generated.&lt;/p&gt;
&lt;p&gt;In fact, I have all of these changes in a single commit. &lt;a href=&#34;https://github.com/megakevin/end-point-blog-dotnet-8-demo/commit/06abed402302316fad980cfbdd0aa9bfdc14aafe&#34;&gt;Here&amp;rsquo;s the diff&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h3 id=&#34;the-plan&#34;&gt;The plan&lt;/h3&gt;
&lt;p&gt;So here&amp;rsquo;s the problem statement: I want to be able to send emails from my .NET app. The body of those emails need to be HTML, and they need to be built based on Razor templates — that is, I want to be able to define &lt;code&gt;*.cshtml&lt;/code&gt; files for them. I also want to be able to define a &amp;ldquo;Mailer&amp;rdquo; class for each specific transaction or event that I want to send emails for. These &amp;ldquo;Mailer&amp;rdquo; classes are what the domain logic components will use directly to send the emails. They are the system&amp;rsquo;s gateway to email sending functionality.&lt;/p&gt;
&lt;p&gt;To fulfill those requirements, we will need four elements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A base component for sending emails.&lt;/li&gt;
&lt;li&gt;A component for turning Razor templates (i.e. &lt;code&gt;*.cshtml&lt;/code&gt; files) into email bodies.&lt;/li&gt;
&lt;li&gt;The actual Razor templates.&lt;/li&gt;
&lt;li&gt;A concrete component that domain logic can invoke to send transactional emails.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;step-1-sending-emails-in-net-with-the-mailkit-nuget-package&#34;&gt;Step 1: Sending emails in .NET with the MailKit NuGet package&lt;/h3&gt;
&lt;p&gt;Creating a class that sends emails is easy using the &lt;a href=&#34;https://github.com/jstedfast/MailKit&#34;&gt;MailKit&lt;/a&gt; NuGet &lt;a href=&#34;https://www.nuget.org/packages/MailKit/&#34;&gt;package&lt;/a&gt;. I ended up using the approach discussed in &lt;a href=&#34;https://mailtrap.io/blog/asp-net-core-send-email/&#34;&gt;this article&lt;/a&gt; by Dzenana Kajtaz for &lt;a href=&#34;https://mailtrap.io/&#34;&gt;Mailtrap&lt;/a&gt;&amp;rsquo;s blog.&lt;/p&gt;
&lt;p&gt;The first thing to do is to install the MailKit NuGet package. This command will do it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet add package MailKit --version 4.5.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The next thing to do is add the necessary SMTP configuration settings into the project&amp;rsquo;s &lt;code&gt;appsettings.json&lt;/code&gt; file. Here&amp;rsquo;s what one might look like when configured to use Mailtrap.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.WebApi/appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;MailSettings&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Server&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sandbox.smtp.mailtrap.io&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;587&lt;/span&gt;, &lt;span style=&#34;color:#888&#34;&gt;// 25 or 465 or 587 or 2525
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;SenderName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;VehicleQuotes&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;SenderEmail&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;system@vehiclequotes.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;UserName&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;YOUR_SMTP_SERVER_USER_NAME&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;&amp;#34;Password&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;YOUR_SMTP_SERVER_USER_PASSWORD&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we define a class that matches the data structure of these settings. Here&amp;rsquo;s the one I ended up with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.WebApi/Configuration/MailSettings.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.WebApi.Configuration&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MailSettings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Server { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;int&lt;/span&gt; Port { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; SenderName { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; SenderEmail { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; UserName { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Password { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, to make the settings actually accessible to the system, we need to add them to the &lt;a href=&#34;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0&#34;&gt;Dependency Injection container&lt;/a&gt;. I added this to the application&amp;rsquo;s bootstrapping logic in my &lt;code&gt;Program.cs&lt;/code&gt; file, before the call to &lt;code&gt;builder.Build()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.Configure&amp;lt;Configuration.MailSettings&amp;gt;(config.GetSection(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings&amp;#34;&lt;/span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This essentially tells .NET to read the &lt;code&gt;&amp;quot;MailSettings&amp;quot;&lt;/code&gt; section from &lt;code&gt;appsettings.json&lt;/code&gt; and load the data in an object of type &lt;code&gt;MailSettings&lt;/code&gt;. This object will be available to any class in the system thanks to Dependency Injection. We will use it later.&lt;/p&gt;
&lt;p&gt;Now we need to define a class for sending emails. This one is low level. All it does is send emails, it doesn&amp;rsquo;t render Razor templates. Other components will take care of that. So for now, here&amp;rsquo;s the class responsible for sending emails:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.WebApi/Services/Mailer.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.Options&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MailKit.Net.Smtp&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MimeKit&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.WebApi.Configuration&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.WebApi.Services&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MailData&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; To { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; ToName { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Subject { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Body { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;IMailer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Task&amp;lt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;bool&lt;/span&gt;&amp;gt; SendMailAsync(MailData mailData);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mailer&lt;/span&gt; : IMailer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; MailSettings _mailSettings;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; Mailer(IOptions&amp;lt;MailSettings&amp;gt; mailSettingsOptions)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _mailSettings = mailSettingsOptions.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;bool&lt;/span&gt;&amp;gt; SendMailAsync(MailData mailData)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;var&lt;/span&gt; emailMessage = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; MimeMessage();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.From.Add(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; MailboxAddress(_mailSettings.SenderName, _mailSettings.SenderEmail));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.To.Add(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; MailboxAddress(mailData.ToName, mailData.To));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.Subject = mailData.Subject;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; emailBodyBuilder = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; BodyBuilder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                TextBody = mailData.Body,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                HtmlBody = mailData.Body
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            emailMessage.Body = emailBodyBuilder.ToMessageBody();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;var&lt;/span&gt; mailClient = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; SmtpClient();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; mailClient.ConnectAsync(_mailSettings.Server, _mailSettings.Port, MailKit.Security.SecureSocketOptions.StartTls);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; mailClient.AuthenticateAsync(_mailSettings.UserName, _mailSettings.Password);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; mailClient.SendAsync(emailMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; mailClient.DisconnectAsync(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;catch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;// TODO: log the email delivery failure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This class is straightforward. It has a single method, &lt;code&gt;SendMailAsync&lt;/code&gt;. The method receives the email subject, body and recipient within a &lt;code&gt;MailData&lt;/code&gt; object. Then, it uses the conventional MailKit process to send the email: builds the message, sets sender and recipient, sets the body, connects to the server, authenticates, and sends the email.&lt;/p&gt;
&lt;p&gt;For this class to be available at runtime, we need to add it to the Dependency Injection container. So, similar to how we did when loading the SMTP server configuration options, we add this line to the &lt;code&gt;Program.cs&lt;/code&gt; file before the call to &lt;code&gt;builder.Build()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;Services.IMailer, Services.Mailer&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ok, with that, our system knows how to send emails. Let&amp;rsquo;s see about the next step now.&lt;/p&gt;
&lt;h3 id=&#34;step-2-rendering-razor-templates-into-strings&#34;&gt;Step 2: Rendering Razor templates into strings&lt;/h3&gt;
&lt;p&gt;Now that we have our core mailer class, we see that it expects a string to use as a body for the emails it sends. So like I mentioned before, we need a component that can take Razor templates (i.e. &lt;code&gt;*.cshtml&lt;/code&gt; files) and turn them into strings. Here&amp;rsquo;s how that&amp;rsquo;s done.&lt;/p&gt;
&lt;p&gt;This component will live in a new &lt;a href=&#34;https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-8.0&amp;amp;tabs=netcore-cli&#34;&gt;Razor Class Library project&lt;/a&gt;. The &lt;code&gt;*.cshtml&lt;/code&gt; templates will also live here. We can create the project and add it to the solution with commands like these:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet new razorclasslib -o VehicleQuotes.RazorTemplates -s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet sln add ./VehicleQuotes.RazorTemplates/VehicleQuotes.RazorTemplates.csproj&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now comes the star of the show, the class that renders Razor templates into strings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Http&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc.Abstractions&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc.ModelBinding&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc.Razor&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc.Rendering&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc.ViewEngines&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Mvc.ViewFeatures&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.AspNetCore.Routing&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.RazorTemplates.Services&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;interface&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;IRazorViewRenderer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Task&amp;lt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt;&amp;gt; Render&amp;lt;TModel&amp;gt;(&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; viewName, TModel model);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;RazorViewRenderer&lt;/span&gt; : IRazorViewRenderer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; IRazorViewEngine _viewEngine;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; ITempDataProvider _tempDataProvider;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; IServiceScopeFactory _serviceScopeFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; RazorViewRenderer(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IRazorViewEngine viewEngine,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ITempDataProvider tempDataProvider,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        IServiceScopeFactory serviceScopeFactory
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _viewEngine = viewEngine;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _tempDataProvider = tempDataProvider;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _serviceScopeFactory = serviceScopeFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt;&amp;gt; Render&amp;lt;TModel&amp;gt;(&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; viewName, TModel model)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;var&lt;/span&gt; scope = _serviceScopeFactory.CreateScope();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; httpContext = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; DefaultHttpContext() { RequestServices = scope.ServiceProvider };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; actionContext = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; ActionContext(httpContext, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; RouteData(), &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; ActionDescriptor());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; view = FindView(actionContext, viewName);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; viewData = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; ViewDataDictionary&amp;lt;TModel&amp;gt;(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; EmptyModelMetadataProvider(), &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; ModelStateDictionary())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Model = model
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; tempData = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; TempDataDictionary(httpContext, _tempDataProvider);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;var&lt;/span&gt; output = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; StringWriter();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; viewContext = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; ViewContext(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            actionContext,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            view,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            viewData,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            tempData,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            output,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; HtmlHelperOptions()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; view.RenderAsync(viewContext);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; output.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; IView FindView(ActionContext actionContext, &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; viewName)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; getViewResult = _viewEngine.GetView(executingFilePath: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;null&lt;/span&gt;, viewPath: viewName, isMainPage: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (getViewResult.Success)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; getViewResult.View;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; findViewResult = _viewEngine.FindView(actionContext, viewName, isMainPage: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (findViewResult.Success)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; findViewResult.View;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; searchedLocations = getViewResult.SearchedLocations.Concat(findViewResult.SearchedLocations);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;var&lt;/span&gt; errorMessage = &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt;.Join(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Environment.NewLine,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;[] { &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;$&amp;#34;Unable to find view &amp;#39;{viewName}&amp;#39;. The following locations were searched:&amp;#34;&lt;/span&gt; }.Concat(searchedLocations)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; InvalidOperationException(errorMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This class is complicated. It leverages several obscure framework components that are not very commonly used. I was able to piece it together with help from &lt;a href=&#34;https://scottsauber.com/2018/07/07/walkthrough-creating-an-html-email-template-with-razor-and-razor-class-libraries-and-rendering-it-from-a-net-standard-class-library/&#34;&gt;here&lt;/a&gt;, &lt;a href=&#34;https://github.com/aspnet/Entropy/blob/master/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs&#34;&gt;here&lt;/a&gt;, and &lt;a href=&#34;https://stackoverflow.com/questions/63802400/return-view-as-string-in-net-core-3-0/64337478#64337478&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s quite a bit of code, but most of it is ceremony in the service of executing two main steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Finding the &lt;code&gt;*.cshtml&lt;/code&gt; file that corresponds to the given &lt;code&gt;viewName&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preparing an &lt;code&gt;IView&lt;/code&gt; object that can be used to render the template. It does so using the given &lt;code&gt;model&lt;/code&gt; object which contains the actual data to fill out the template placeholders.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally, in order to make this class available to the system, we add it to Dependency Injection. Here&amp;rsquo;s what that looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddMvcCore().AddRazorViewEngine(); &lt;span style=&#34;color:#888&#34;&gt;// Necessary for non-GUI projects.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;RazorTemplates.Services.IRazorViewRenderer, RazorTemplates.Services.RazorViewRenderer&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The only interesting thing here is the &lt;code&gt;services.AddMvcCore().AddRazorViewEngine();&lt;/code&gt; line. I had to add that to my project because it is a Web API. That means that it doesn&amp;rsquo;t include all the services related to rendering views. Other project types that already include all the view-related services, like MVC or Razor Pages, may not need this line. Remember that our &lt;code&gt;RazorViewRenderer&lt;/code&gt; class depends on all sorts of framework objects. This line makes sure that they are available.&lt;/p&gt;
&lt;h3 id=&#34;step-3-defining-the-email-templates&#34;&gt;Step 3: Defining the email templates&lt;/h3&gt;
&lt;p&gt;Now that our system knows how to render Razor templates into strings, let&amp;rsquo;s go ahead and actually implement some. The nice thing about this approach is that these templates are full Razor views. That means that features like layouts and partials are supported.&lt;/p&gt;
&lt;p&gt;For the purposes of this demo, a simple layout with a header and a footer will suffice. Along with the body of the particular transactional email that we want to send.&lt;/p&gt;
&lt;p&gt;Our layout could look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- VehicleQuotes.RazorTemplates/Views/Shared/EmailLayout.cshtml --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#c00;font-weight:bold&#34;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;lang&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;charset&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;name&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;content&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;Email With Razor Templates&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Imagine this is a header&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @RenderBody()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Imagine this is a footer&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;html&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Very simple as layouts go. It does little more than define the basic HTML document structure and call &lt;code&gt;@RenderBody()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can also add a &lt;code&gt;_ViewStart.cshtml&lt;/code&gt; file that specifies this layout as the layout to use for all other templates under the &lt;code&gt;Emails&lt;/code&gt; directory:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- VehicleQuotes.RazorTemplates/Views/Emails/_ViewStart.cshtml --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Layout = &amp;#34;EmailLayout&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, for the core contents of the email, we define a new template, along with a class that will serve as its view model.&lt;/p&gt;
&lt;p&gt;My template ended up looking like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&amp;lt;!-- VehicleQuotes.RazorTemplates/Views/Emails/QuoteGenerated.cshtml --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@using VehicleQuotes.RazorTemplates.ViewModels
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@model QuoteGeneratedViewModel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    A quote has been generated for a @Model.Year @Model.Make @Model.Model.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Quote ID: @Model.ID&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Created At: @Model.CreatedAt.ToLongDateString()&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Offered Amount: @Model.OfferedQuote&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Message: @Model.Message&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the view model:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.RazorTemplates/ViewModels/QuoteGeneratedViewModel.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.RazorTemplates.ViewModels&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;QuoteGeneratedViewModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;int&lt;/span&gt; ID { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required DateTime CreatedAt { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;int&lt;/span&gt; OfferedQuote { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Message { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Year { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Make { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; required &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; Model { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;get&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Very simple. The template specifies the type that it accepts as a view model using the &lt;code&gt;@model&lt;/code&gt; directive. It then proceeds to render the email contents using regular old Razor syntax. The view model is just a simple &lt;a href=&#34;https://en.wikipedia.org/wiki/Plain_old_CLR_object&#34;&gt;POCO&lt;/a&gt; that defines the data that the template can work with.&lt;/p&gt;
&lt;h3 id=&#34;step-4-putting-it-all-together-the-class-that-sends-the-quote-generated-email&#34;&gt;Step 4: Putting it all together: the class that sends the &amp;ldquo;quote generated&amp;rdquo; email&lt;/h3&gt;
&lt;p&gt;Finally, we can define our specific Mailer class that, leveraging all the infrastructure we&amp;rsquo;ve put together, can send one specific type of transactional email. These classes are meant to be simple and boring. Here&amp;rsquo;s mine:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.WebApi/Services/QuoteGeneratedMailer.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.RazorTemplates.Services&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.WebApi.ResourceModels&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.RazorTemplates.ViewModels&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;VehicleQuotes.WebApi.Services&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;QuoteGeneratedMailer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; IMailer _mailer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;readonly&lt;/span&gt; IRazorViewRenderer _razorViewRenderer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; QuoteGeneratedMailer(IMailer mailer, IRazorViewRenderer razorViewRenderer)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _mailer = mailer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        _razorViewRenderer = razorViewRenderer;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;async&lt;/span&gt; Task SendAsync(QuoteGeneratedViewModel payload)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;string&lt;/span&gt; body = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _razorViewRenderer.Render(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/Views/Emails/QuoteGenerated.cshtml&amp;#34;&lt;/span&gt;, payload
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _mailer.SendMailAsync(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            To = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;test@email.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ToName = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Mr. Recipient&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Subject = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;$&amp;#34;VehicleQuotes - New Quote Generated - Quote #{payload.ID}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Body = body
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pretty neat, huh? This class receives instances of the base &lt;code&gt;Mailer&lt;/code&gt; and the &lt;code&gt;RazorViewRenderer&lt;/code&gt; via Dependency Injection and uses them to: 1. render the template and 2. send the email.&lt;/p&gt;
&lt;p&gt;Like everything else, it also needs to be made available via Dependency Injection. All in all, I ended up with this nice bundle in my &lt;code&gt;Program.cs&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.Configure&amp;lt;Configuration.MailSettings&amp;gt;(config.GetSection(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;MailSettings&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddScoped&amp;lt;Services.QuoteGeneratedMailer&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddMvcCore().AddRazorViewEngine();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;RazorTemplates.Services.IRazorViewRenderer, RazorTemplates.Services.RazorViewRenderer&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;Services.IMailer, Services.Mailer&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A good idea is to put these into an &lt;code&gt;IServiceCollection&lt;/code&gt; extension method. That&amp;rsquo;s what I ended up doing, in fact.&lt;/p&gt;
&lt;p&gt;Instances of a class like this can be used anywhere in the code. For example like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// VehicleQuotes.WebApi/Services/QuoteService.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// _mailer is a QuoteGeneratedMailer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;// Imagine response is an object that contains all these fields.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;await&lt;/span&gt; _mailer.SendAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; QuoteGeneratedViewModel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ID = response.ID,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CreatedAt = response.CreatedAt,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        OfferedQuote = response.OfferedQuote,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Message = response.Message,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Year = response.Year,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Make = response.Make,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Model = response.Model
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Just build the view model object that it expects and off it goes.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it! That definitely took some elbow grease to get working as well as delving into pretty arcane framework features. In the end, however, we did manage to build something that offers a developer experience that&amp;rsquo;s very similar to Action Mailer. Once the core &lt;code&gt;Mailer&lt;/code&gt; and the &lt;code&gt;RazorViewRenderer&lt;/code&gt; are in place, all it takes to send a new transactional email is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Defining a new template, with its view model.&lt;/li&gt;
&lt;li&gt;Defining a new Mailer class that renders the template, uses it as the email&amp;rsquo;s body, and sends it.&lt;/li&gt;
&lt;/ol&gt;

      </content>
    </entry>
  
    <entry>
      <title>Working around SPF problems delivering to Gmail</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/03/spf-problems-gmail-workaround/"/>
      <id>https://www.endpointdev.com/blog/2022/03/spf-problems-gmail-workaround/</id>
      <published>2022-03-30T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/03/spf-problems-gmail-workaround/20220321_194242-sm.webp&#34; alt=&#34;Hand-drawn signs reading &amp;ldquo;Someplace&amp;rdquo;, &amp;ldquo;Any pla…&amp;rdquo;, &amp;ldquo;No place&amp;rdquo;, with arrows pointing variously, attached to a leaning signpost in front of a high mountain desert scene with snow-topped peaks and sagebrush&#34;&gt;
Photo by Garrett Skinner&lt;/p&gt;
&lt;h3 id=&#34;email-deliverability&#34;&gt;Email deliverability&lt;/h3&gt;
&lt;p&gt;Legitimate email delivery keeps getting harder. Spammers and phishers never stop flooding everyone&amp;rsquo;s inboxes with unwanted and harmful email, so automated defenses against junk mail are necessary. But they are not perfect, and good email sometimes gets flagged as spam.&lt;/p&gt;
&lt;p&gt;When sending important &amp;ldquo;transactional&amp;rdquo; email such as for account confirmations, password resets, and ecommerce receipts, it is often worth using a paid email delivery service to increase deliverability. Those typically cost a flat amount per month for up to a certain quota of outgoing email, with overage charges for messages beyond that.&lt;/p&gt;
&lt;p&gt;Many of our clients use one of those services and generally they have all worked well and differ mostly in pricing and feature set. Popular choices include SendGrid, Mandrill, Postmark, Mailgun, and Amazon SES.&lt;/p&gt;
&lt;p&gt;We continue to have many cases where we want to be able to send potentially large amounts of automated email to ourselves, our clients, or our systems. This is usually for testing, notifications, or internal delivery to special mailboxes separate from our main mailboxes.&lt;/p&gt;
&lt;p&gt;These other uses for sending email keep us involved in the fight for good email deliverability from our own servers, which we have worked at over many years, long predating these paid email delivery services.&lt;/p&gt;
&lt;h3 id=&#34;sender-policy-framework&#34;&gt;Sender Policy Framework&lt;/h3&gt;
&lt;p&gt;One of the longest-running tools to fight spam is SPF, the Sender Policy Framework.&lt;/p&gt;
&lt;p&gt;SPF is an open standard that provides a way for a receiving mail server to verify that the sending server is authorized to send email for the message&amp;rsquo;s &amp;ldquo;envelope&amp;rdquo; sender domain. The envelope sender address or &amp;ldquo;return-path&amp;rdquo; is not normally seen by email recipients, but is used behind the scenes by servers. It may or may not be the same as the sender seen in the &amp;ldquo;From&amp;rdquo; header.&lt;/p&gt;
&lt;p&gt;The SPF policy for each domain is set in a special DNS TXT record for that domain.&lt;/p&gt;
&lt;p&gt;The important thing is that each sender&amp;rsquo;s email belongs to a domain with a valid SPF record showing that the sending servers are allowed to send for that domain, and that all other servers should &lt;em&gt;not&lt;/em&gt; be allowed to send email for that domain.&lt;/p&gt;
&lt;p&gt;For example, our endpointdev.com domain currently has this TXT record to define its SPF policy:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;v=spf1 a:maildrop.endpointdev.com include:_spf.google.com include:servers.mcsv.net -all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s look at each of those space-separated elements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v=spf1&lt;/code&gt; designates this TXT record as an SPF policy, version 1 (the only one so far).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a:maildrop.endpointdev.com&lt;/code&gt; means to allow the A (IPv4) and/or AAAA (IPv6) IP address(es) of hostname maildrop.endpointdev.com as a valid source.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;include:_spf.google.com&lt;/code&gt; means to look up another DNS TXT record at _spf.google.com (for Gmail, our main email provider here) and add its SPF policy to ours.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;include:servers.mcsv.net&lt;/code&gt; is the same thing, but for servers.mcsv.net (for Mailchimp, to allow it to deliver email newsletters for our domain).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-all&lt;/code&gt; means to disallow any other senders.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With such a policy, receiving mail servers can immediately reject any incoming email claiming to be sent by us for our domain if it didn&amp;rsquo;t come from one of our designated servers.&lt;/p&gt;
&lt;p&gt;This obviously doesn&amp;rsquo;t stop all spam, but it stops a whole class of forged senders, which is very helpful.&lt;/p&gt;
&lt;p&gt;The key point to note is that SPF applies to the sending email server at the moment it connects to the receiving email server. It doesn&amp;rsquo;t deal with anything else.&lt;/p&gt;
&lt;p&gt;One other point to note is that SPF policies are limited to a fairly small total number of DNS lookups via &lt;code&gt;include&lt;/code&gt; elements, so we can&amp;rsquo;t endlessly add new valid sending servers to our list.&lt;/p&gt;
&lt;h3 id=&#34;email-server-trails&#34;&gt;Email server trails&lt;/h3&gt;
&lt;p&gt;Based on the above SPF policy, if we want to send email from address &lt;code&gt;notifier@endpointdev.com&lt;/code&gt;, it will have to be sent through &lt;code&gt;maildrop.endpointdev.com&lt;/code&gt;, Gmail, or Mailchimp. Messages coming from any other sending server should be rejected by the receiving server. They don&amp;rsquo;t have to behave that way, but it is in their interest to do so if they don&amp;rsquo;t like spam.&lt;/p&gt;
&lt;p&gt;We have an internal server we&amp;rsquo;ll call &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt;, which sends email notifications from address &lt;code&gt;notifier@endpointdev.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since we don&amp;rsquo;t want to bloat our SPF policy, we&amp;rsquo;ll have our server &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt; route its outgoing email through our mail forwarding service called maildrop, which lives on two or more servers behind the DNS name &lt;code&gt;maildrop.endpointdev.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a good idea for several reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It keeps all our outgoing email flowing through a few places so we can easily monitor them for any problems.&lt;/li&gt;
&lt;li&gt;We don&amp;rsquo;t need to have SMTP daemons running on all our servers just to send outbound email.&lt;/li&gt;
&lt;li&gt;We don&amp;rsquo;t need to worry about the quotas or pricing of commercial emailing services when sending less-important or internal-only email.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since SPF is designed for a receiving email server to check that the server connecting to it to send email is authorized to do so for that email address&amp;rsquo;s domain, it shouldn&amp;rsquo;t matter what server the email originated on.&lt;/p&gt;
&lt;h3 id=&#34;gmail-misuses-header-information-in-spf-checks&#34;&gt;Gmail misuses header information in SPF checks&lt;/h3&gt;
&lt;p&gt;We recently discovered that Gmail has been misusing email header information in its SPF checks.&lt;/p&gt;
&lt;p&gt;When one of our outgoing emails originated from server &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt; and was then forwarded to &lt;code&gt;maildrop.endpointdev.com&lt;/code&gt; which then delivered it to Gmail, Gmail looked at the earliest sender server it could find in the &lt;code&gt;Received&lt;/code&gt; headers of the email message, found &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt;, and flagged it as an SPF failure because our SPF policy didn&amp;rsquo;t include &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt; [206.191.128.233].&lt;/p&gt;
&lt;p&gt;This can be seen in this excerpt of relevant email headers. (Some specific details here were changed to protect the innocent.) Note that email headers appear in reverse chronological order, so the most recent events are at the top:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Received: from maildrop14.epinfra.net (maildrop14.epinfra.net. [69.25.178.35])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        by mx.google.com with ESMTPS id l20si5561179oos.78.2022.01.25.10.52.05
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        for &amp;lt;notifications@endpointdev.com&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Tue, 25 Jan 2022 10:52:05 -0800 (PST)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Received-SPF: fail (google.com: domain of notifier@endpointdev.com does not designate 206.191.128.233 as permitted
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sender) client-ip=206.191.128.233;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Authentication-Results: mx.google.com;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       dkim=pass header.i=@endpointdev.com header.s=maildrop header.b=hR445V77;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       spf=fail (google.com: domain of notifier@endpointdev.com does not designate 206.191.128.233 as permitted
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sender) smtp.mailfrom=notifier@endpointdev.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Received: from dashboard.endpointdev.com (dashboard.endpointdev.com [206.191.128.233])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    by maildrop14.epinfra.net (Postfix) with ESMTP id A2AA03E8A7
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    for &amp;lt;notifications@endpointdev.com&amp;gt;; Tue, 25 Jan 2022 18:52:05 +0000 (UTC)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;To: &amp;lt;notifications@endpointdev.com&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That is wrong! The SPF check should have been done against &lt;code&gt;maildrop14.epinfra.net&lt;/code&gt; [69.25.178.35] because that is the IP address that actually connected to Gmail to send the email. That server is one of our infrastructure hostnames allowed to send email as part of the &lt;code&gt;maildrop.endpointdev.com&lt;/code&gt; DNS record, so checking it would have led Gmail to give a passing SPF result.&lt;/p&gt;
&lt;p&gt;Why did Gmail do this? I don&amp;rsquo;t know, and at the time didn&amp;rsquo;t find any public discussion that would explain it. I suspect it has something to do with Gmail&amp;rsquo;s internal systems being comprised of many, many servers, and the SPF check being done long after the email was passed on from the initial receiving point through various other servers. Then Gmail parses the headers to find out who the sender was, and gets confused.&lt;/p&gt;
&lt;h3 id=&#34;dont-share-tmi&#34;&gt;Don&amp;rsquo;t share TMI&lt;/h3&gt;
&lt;p&gt;We can avoid this problem by not having maildrop mention our original sending server &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt; at all.&lt;/p&gt;
&lt;p&gt;Why should it mention it in the first place? It&amp;rsquo;s helpful for tracing problems when debugging, but really is TMI (too much information) for normal email sending, and exposes internal infrastructure details that would be better omitted anyway.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt; is running the very flexible and configurable Postfix email server, we can direct it to remove any &lt;code&gt;Received&lt;/code&gt; headers that mention our internal hostnames.&lt;/p&gt;
&lt;p&gt;By default Postfix in &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt; has the &lt;code&gt;header_checks&lt;/code&gt; directive set to look at a table to match regular expressions and take specified actions.&lt;/p&gt;
&lt;p&gt;So we added a regular expression to match and designated the action &lt;code&gt;IGNORE&lt;/code&gt;, to the file &lt;code&gt;/etc/postfix/header_checks&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/^Received:\ (from|by)\ .*(epinfra\.net|endpointdev\.com|localhost|localdomain)/  IGNORE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we update the map database file so that it takes immediate effect for new email flowing through Postfix:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postmap /etc/postfix/header_checks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When we sent another notification email from &lt;code&gt;dashboard.endpointdev.com&lt;/code&gt; and received it in Gmail we saw the email&amp;rsquo;s headers look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Received: from maildrop14.epinfra.net (maildrop14.epinfra.net. [69.25.178.35])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        by mx.google.com with ESMTPS id g72si1894187vke.271.2022.01.25.11.03.37
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        for &amp;lt;notifications@endpointdev.com&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        Tue, 25 Jan 2022 11:03:37 -0800 (PST)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Received-SPF: pass (google.com: domain endpointdev.com configured 69.25.178.35 as internal address)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Authentication-Results: mx.google.com;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       dkim=pass header.i=@endpointdev.com header.s=maildrop header.b=qkccUkkU;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       spf=pass (google.com: domain endpointdev.com configured 69.25.178.35 as internal address)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    smtp.mailfrom=notifier@endpointdev.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;To: &amp;lt;notifications@endpointdev.com&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There is no more mention of &lt;code&gt;dashboard&lt;/code&gt; or its IP address, so Gmail runs its SPF check against the proper IP address 69.25.178.35 which belongs to server &lt;code&gt;maildrop14.epinfra.net&lt;/code&gt; which is part of the &lt;code&gt;maildrop.endpointdev.com&lt;/code&gt; DNS name. Gmail now validates that IP address is allowed to send for the endpointdev.com domain and gives a &amp;ldquo;pass&amp;rdquo; result for its SPF check.&lt;/p&gt;
&lt;p&gt;Perhaps this will help your legitimate email delivery too!&lt;/p&gt;
&lt;h3 id=&#34;reference&#34;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.open-spf.org/Introduction/&#34;&gt;SPF Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.postfix.org/&#34;&gt;Postfix mail server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Capturing Outgoing Email With Mock SMTP Servers</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/03/mock-smtp-servers/"/>
      <id>https://www.endpointdev.com/blog/2020/03/mock-smtp-servers/</id>
      <published>2020-03-13T00:00:00+00:00</published>
      <author>
        <name>Patrick Lewis</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/03/mock-smtp-servers/banner.jpg&#34; alt=&#34;Mailboxes&#34; /&gt; &lt;a href=&#34;http://web.archive.org/web/20190215161746/https://www.flickr.com/photos/seattleye/2891561034/&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;http://web.archive.org/web/20191122092817/https://www.flickr.com/photos/seattleye/&#34;&gt;Seattleye&lt;/a&gt;, used under &lt;a href=&#34;https://creativecommons.org/licenses/by/2.0/&#34;&gt;CC BY 2.0&lt;/a&gt;, cropped from original.&lt;/p&gt;
&lt;p&gt;Sending automated email to users is a common requirement of most web applications and can take the form of things like password reset emails or order confirmation invoices.&lt;/p&gt;
&lt;p&gt;It is important for developers working in development/staging environments to verify that an application is sending email correctly without &lt;em&gt;actually&lt;/em&gt; delivering messages to users’ inboxes. If you were testing a background task that searches an e-commerce site for abandoned shopping carts and emails users to remind them that they have not completed a checkout, you would not want to run that in development and end up repeatedly emailing live user email addresses.&lt;/p&gt;
&lt;p&gt;A mock SMTP server is useful for development and testing because it lets you configure the email settings of your development environment almost exactly the same as you would for outgoing SMTP email in your production site. The mock SMTP server will capture all of the outbound email and allow you to review it in a web interface instead of actually delivering it to users’ inboxes.&lt;/p&gt;
&lt;h3 id=&#34;mock-smtp-servers&#34;&gt;Mock SMTP Servers&lt;/h3&gt;
&lt;p&gt;There are a variety of standalone/free and hosted/commercial options for mock SMTP servers including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/mailhog/MailHog&#34;&gt;MailHog&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mailslurper.com&#34;&gt;MailSlurper&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mailcatcher.me&#34;&gt;MailCatcher&lt;/a&gt; (free)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mailtrap.io&#34;&gt;Mailtrap&lt;/a&gt; (free for small solo projects/paid)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mailosaur.com&#34;&gt;Mailosaur&lt;/a&gt; (paid)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The standalone/free options have been sufficient for the projects I have worked on. Some of the features offered by the hosted solutions like Mailtrap and Mailosaur may be appealing to larger development teams.&lt;/p&gt;
&lt;p&gt;MailHog is my go-to mock SMTP server because it has a nice web interface and is extremely easy to install and configure for typical use. The standalone solutions that I have tried all work similarly; they listen for SMTP email on one port, and provide a web interface on a separate port for reviewing captured email.&lt;/p&gt;
&lt;h3 id=&#34;configuring-a-rails-application-to-use-mailhog&#34;&gt;Configuring a Rails Application to use MailHog&lt;/h3&gt;
&lt;p&gt;Installation and use of MailHog is very simple: download and run the &lt;code&gt;mailhog&lt;/code&gt; executable to have it start listening for SMTP email on port 1025 and hosting a web interface on port 8025. Then edit the Rails development environment config file to send email using an SMTP server running on local port 1025:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# config/environments/development.rb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Rails&lt;/span&gt;.application.configure &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# …&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config.action_mailer.delivery_method = &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:smtp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config.action_mailer.perform_deliveries = &lt;span style=&#34;color:#080&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config.action_mailer.smtp_settings = { &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;address&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;port&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1025&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# …&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;MailHog runs in the foreground and will display output as it receives outbound email. Opening a web browser to http://localhost:8025 shows MailHog’s web interface and allows you to view a list of the email that it has captured or click on an email to show its full details:&lt;/p&gt;
&lt;img src=&#34;/blog/2020/03/mock-smtp-servers/mailhog.png&#34; alt=&#34;Mailhog screenshot&#34; /&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I have found MailHog to be helpful when developing a variety of different web applications, particularly ones that generate email for user notifications and confirmations. Thanks to its ease of installation and use, I would recommend it (or an equivalent mock SMTP solution) to any developer that needs to test outgoing email features in their web applications.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Linux desktop Postfix queue for Gmail SMTP</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/04/postfix-gmail-forwarder/"/>
      <id>https://www.endpointdev.com/blog/2019/04/postfix-gmail-forwarder/</id>
      <published>2019-04-30T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;img src=&#34;/blog/2019/04/postfix-gmail-forwarder/20190216-123757-crop.jpg&#34; alt=&#34;Winter view of snow, river, trees, mountains, clouds at Flagg Ranch, Rockefeller Parkway, Wyoming&#34; /&gt;
&lt;p&gt;On a Linux desktop, I want to start sending email through Gmail in a G Suite account using SMTP, rather than a self-hosted SMTP server. Since Gmail supports SMTP, that should be easy enough.&lt;/p&gt;
&lt;p&gt;Google’s article &lt;a href=&#34;https://support.google.com/a/answer/176600?hl=en&#34;&gt;Send email from a printer, scanner, or app&lt;/a&gt; gives an overview of several options. I’ll choose the “Gmail SMTP server” track, which seems designed for individual user cases like this.&lt;/p&gt;
&lt;p&gt;However, since I am using two-factor authentication (2FA) on this Google account — as we should all be doing now for all accounts wherever possible! — my Gmail login won’t work for SMTP because the clients I am using don’t have a way to supply the 2FA time-based token.&lt;/p&gt;
&lt;p&gt;Google’s solution to this is to have me generate a separate “App Password” that can sidestep 2FA for this limited purpose: &lt;a href=&#34;https://support.google.com/mail/answer/185833&#34;&gt;Set up an App Password&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That works fine, but the app password is a randomly-generated 16-letter password that is not amenable to being memorized. For security reasons, my mail client doesn’t cache passwords between sessions, so I have to look it up and enter it each time I start the mail client. That’s generally only once per day for me, so it’s not a big problem, but it would be nice to avoid.&lt;/p&gt;
&lt;p&gt;I also want other local programs — such as cron jobs, development projects underway, etc. — to be able to send mail out through my Gmail account. How can I do that, ideally without teaching each one separately how to do it?&lt;/p&gt;
&lt;p&gt;As a server operating system at heart, Linux of course has many SMTP servers that can intermediate by acting as a local SMTP server, queue, and sending client. Such a server could have my Gmail password configured and stored under a separate user account, giving a bit more isolation from my main desktop user.&lt;/p&gt;
&lt;h3 id=&#34;what-local-smtp-program-to-use&#34;&gt;What local SMTP program to use?&lt;/h3&gt;
&lt;h4 id=&#34;esmtp&#34;&gt;esmtp&lt;/h4&gt;
&lt;p&gt;I first tried using the lightweight and ephemeral &lt;code&gt;esmtp&lt;/code&gt; since I had already used it on my desktop computer to forward email through an SSH tunnel. I wasn’t able to get it working with Gmail, which could easily have been operator error on my part.&lt;/p&gt;
&lt;p&gt;Before trying much to solve the problem, I realized that I really would like a local queue for outgoing email so I don’t have to wait for my mail client to connect and send each message before I can get on with more email. Given how much email I handle each day, even fairly brief delays add unwanted drag.&lt;/p&gt;
&lt;h4 id=&#34;ssmtp&#34;&gt;ssmtp&lt;/h4&gt;
&lt;p&gt;I used another similar program called &lt;code&gt;ssmtp&lt;/code&gt; a long time ago, and considered trying that again.&lt;/p&gt;
&lt;p&gt;Then I read that ssmtp is unmaintained and does not validate TLS certificates, negating some of the security value of using TLS in the first place. So, no to that.&lt;/p&gt;
&lt;h4 id=&#34;e-mailrelay&#34;&gt;E-MailRelay&lt;/h4&gt;
&lt;p&gt;I next saw a few people mention that &lt;code&gt;E-MailRelay&lt;/code&gt; is a nice option to locally queue and forward email. But looking at a new and more comprehensive email daemon like that, I realized I would likely find it easier to just use an SMTP server I already know, such as Exim, Sendmail, or …&lt;/p&gt;
&lt;h4 id=&#34;postfix&#34;&gt;Postfix&lt;/h4&gt;
&lt;p&gt;Postfix is one of the most widely-deployed mail servers. I already know it well and have used it for many years. It has most every option I would ever want. So I’ll use that.&lt;/p&gt;
&lt;p&gt;This seems like a slight bit of overkill for just sending outgoing email, but Postfix is battle-hardened and comparatively lightweight, using around 30 MiB resident RAM between its 4 daemon processes on my computer. Yes, that is laughably bloated by the standards of yore, but svelte compared to even a single tab in a modern graphical browser.&lt;/p&gt;
&lt;h3 id=&#34;configuring-postfix&#34;&gt;Configuring Postfix&lt;/h3&gt;
&lt;p&gt;(You’ll need to be root to do the following setup.)&lt;/p&gt;
&lt;p&gt;First, install Postfix as appropriate for your Linux distribution:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# dnf install postfix    # Fedora
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# yum install postfix    # CentOS/​RHEL
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# apt install postfix    # Debian/​Ubuntu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, time to edit the default Postfix configuration. The options are all &lt;a href=&#34;http://www.postfix.org/postconf.5.html&#34;&gt;well-documented on the Postfix website&lt;/a&gt; but there are a &lt;em&gt;lot&lt;/em&gt; of them and it can take a while to figure out what you need and want.&lt;/p&gt;
&lt;p&gt;Here is what I added to &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;relayhost = [smtp.gmail.com]:465
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_tls_wrappermode = yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_tls_security_level = verify
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_tls_mandatory_protocols = !SSLv2, !TLSv1, !TLSv1.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tls_high_cipherlist = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_tls_mandatory_ciphers = high
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_tls_loglevel = 2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_sasl_auth_enable = yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_sasl_mechanism_filter = plain
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_sasl_security_options = noanonymous
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;header_checks = regexp:/etc/postfix/header_checks
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp_address_preference = ipv6&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let’s go through each of those settings:&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;relayhost&lt;/code&gt; we define the remote server we want to route all our outgoing mail through. The &lt;code&gt;[...]&lt;/code&gt; around the hostname disables MX lookups, since there is no MX record for smtp.gmail.com. Port 465 is used for TLS-wrapped (called “implicit”) mail submission, so we also need to set &lt;code&gt;smtp_tls_wrappermode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I used the port number 465 rather than the name &lt;code&gt;smtps&lt;/code&gt; because the port has been reassigned for another purpose, so it may be mentioned in the &lt;code&gt;/etc/services&lt;/code&gt; file under either the old or new names or both, and isn’t worth risking breakage for.&lt;/p&gt;
&lt;p&gt;Enabling &lt;code&gt;smtp_tls_wrappermode&lt;/code&gt; means we initiate a TLS connection immediately, rather than first connecting with classic plain-text SMTP protocol, and then requesting a switch to TLS mode with the &lt;code&gt;STARTTLS&lt;/code&gt; SMTP verb. This option is why we specified port 465 above for our &lt;code&gt;relayhost&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Like me, you may recall the option for SSL-wrapped SMTP to port 465 as little more than an odd Microsoft Exchange and Outlook convention from years ago. Once upon a time it was. Why not use the more standard submission port 587? Up until fairly recently that would have been best.&lt;/p&gt;
&lt;p&gt;In a funny turn of events, nowadays TLS-wrapped SMTP on port 465 is recommended as the best way for end-users to submit mail! See &lt;a href=&#34;https://tools.ietf.org/html/rfc8314#section-3&#34;&gt;RFC 8314&lt;/a&gt; for details. I found the helpful &lt;a href=&#34;https://www.fastmail.com/help/technical/ssltlsstarttls.html&#34;&gt;FastMail explanation of all the historical twists&lt;/a&gt; well worth reading. As they say, the best thing about standards is that there are so many to choose from. 😆&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;smtp_tls_security_level&lt;/code&gt; to &lt;code&gt;verify&lt;/code&gt; means that we want TLS to be mandatory for every connection, and we want Postfix to validate that the certificate name matches the hostname we are using to connect to prevent man-in-​the-​middle (MITM) attacks.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;smtp_tls_mandatory_protocols&lt;/code&gt; we are disabling all older TLS protocols, allowing only the most current (as of this writing) versions 1.2 and 1.3. That would be overly restrictive and unsafe to do on a public mail server, but since we are only talking to a single mail server here, and we know that Google supports modern TLS, we can restrict ourselves to that.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;tls_high_cipherlist&lt;/code&gt; list is our restricted set of TLS ciphers in our preferred order. Again, this would be unwise to do on a public mail server, but in our role as a client forwarding to only one destination here, it is a good thing. My list comes from &lt;a href=&#34;https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility&#34;&gt;Mozilla’s modern TLS recommendation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;smtp_tls_mandatory_ciphers&lt;/code&gt; set to &lt;code&gt;high&lt;/code&gt;, we ensure the list we just specified gets used.&lt;/p&gt;
&lt;p&gt;I set &lt;code&gt;smtp_tls_loglevel&lt;/code&gt; to 2 so that the Postfix logs will show helpful details about TLS connection negotiations and results.&lt;/p&gt;
&lt;p&gt;The next several &lt;code&gt;smtp_sasl_&lt;/code&gt; options configure our use of a username and password. (SASL means “Simple Authentication and Security Layer”.) Note the &lt;code&gt;smtp_sasl_password_maps&lt;/code&gt; file we specified. The file name is arbitrary. Let’s create that now in &lt;code&gt;/etc/postfix/smtp_auth&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[smtp.gmail.com]:465    user@gsuite.domain:apppassword&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course substitute your own email address and Gmail app password there.&lt;/p&gt;
&lt;p&gt;We need to create a fast binary map equivalent of that file for Postfix to read, and since it contains a password that should be kept private, let’s make it unreadable by other users on the system:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# postmap hash:smtp_auth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# chmod go= smtp_auth*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next I set option &lt;code&gt;header_checks&lt;/code&gt; to look for regular expressions in another file we need to create, &lt;code&gt;/etc/postfix/header_checks&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/^Received:\ (from|by)\ .*(yourhostname|localhost|localdomain)/      IGNORE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That removes a pair of headers that Postfix normally adds to each message we send, tracking the receipt and forwarding on of the email:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Received: by yourhostname.localdomain (Postfix, from userid 1000)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    id F2F7111C64A1; Tue, 30 Apr 2019 17:36:12 -0600 (MDT)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Received: from localhost (localhost [127.0.0.1])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    by yourhostname.localdomain (Postfix) with ESMTP id EE7DB11C161C;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Tue, 30 Apr 2019 17:36:12 -0600 (MDT)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In our case those are a waste of space because it is not interesting to track the flow of email around localhost. So we just remove them before sending the mail on.&lt;/p&gt;
&lt;p&gt;Finally, a minor nicety I like to enable is to set &lt;code&gt;smtp_address_preference&lt;/code&gt; to &lt;code&gt;ipv6&lt;/code&gt; so that when we have an IPv6 connection to the outside world, that is used to send the mail. An IPv4 connection will still be used if IPv6 isn’t available. I figure we’d might as well use the newer routing tubes of the Internet when we can.&lt;/p&gt;
&lt;h3 id=&#34;trying-it-out&#34;&gt;Trying it out&lt;/h3&gt;
&lt;p&gt;Now we’re ready to start Postfix, and set it to start automatically at boot time:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# systemctl start postfix
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# systemctl enable postfix&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you need to configure your mail client. My enthusiasm for &lt;a href=&#34;https://en.wikipedia.org/wiki/Alpine_%28email_client%29&#34;&gt;Pine (now Alpine)&lt;/a&gt; hasn’t waned after over 20 years, so that is what I use. Perhaps you prefer &lt;a href=&#34;https://en.wikipedia.org/wiki/Mutt_(email_client)&#34;&gt;Mutt&lt;/a&gt; or &lt;a href=&#34;https://www.thunderbird.net/&#34;&gt;Thunderbird&lt;/a&gt; or something else.&lt;/p&gt;
&lt;p&gt;Whatever it is, set your mail client to send mail through local Postfix executable &lt;code&gt;/usr/sbin/sendmail&lt;/code&gt; (the default for many), or through SMTP to &lt;code&gt;localhost:25&lt;/code&gt;. For me, that meant editing &lt;code&gt;~/.pinerc&lt;/code&gt;. When I was sending mail directly from Pine to Gmail, it contained:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp-server=smtp.gmail.com/submit/tls/user=you@your.domain&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But now I want there to be no setting so it falls back to &lt;code&gt;/usr/sbin/sendmail&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtp-server=&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now watch your Postfix logs, usually in &lt;code&gt;/var/log/mail.log&lt;/code&gt; on Debian and Ubuntu systems, and &lt;code&gt;/var/log/maillog&lt;/code&gt; on Fedora and CentOS.&lt;/p&gt;
&lt;p&gt;And send a message!&lt;/p&gt;
&lt;p&gt;Let’s look at what Postfix logged when I sent a test message.&lt;/p&gt;
&lt;p&gt;When using rsyslog my complete log lines look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2019-04-30T17:59:11.887004-06:00 localhost postfix/smtpd[10962]: connect from localhost[127.0.0.1]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That timestamp and hostname prefix waste a lot of room on each line here, so I will trim them from the rest of the xample:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtpd[10962]: connect from localhost[127.0.0.1]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtpd[10962]: DA75311C649C: client=localhost[127.0.0.1]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtpd[10962]: disconnect from localhost[127.0.0.1] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/pickup[10947]: DE8FE11C64A1: uid=1000 from=&amp;lt;you@your.domain&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/cleanup[10964]: DE8FE11C64A1: message-id=&amp;lt;f15302b5-cd03-488a-a401-e1eb8544895f@ybpnyubfg&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/qmgr[10948]: DE8FE11C64A1: from=&amp;lt;you@your.domain&amp;gt;, size=393, nrcpt=1 (queue active)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: initializing the client-side TLS engine
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: setting up TLS connection to smtp.gmail.com[2607:f8b0:4001:c03::6c]:465
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: TLS cipher list &amp;#34;ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:!aNULL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:before SSL initialization
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS write client hello
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS write client hello
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS read server hello
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: depth=2 verify=1 subject=/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: depth=1 verify=1 subject=/C=US/O=Google Trust Services/CN=Google Internet Authority G3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: depth=0 verify=1 subject=/C=US/ST=California/L=Mountain View/O=Google LLC/CN=smtp.gmail.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS read server certificate
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS read server key exchange
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS read server done
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS write client key exchange
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS write change cipher spec
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS write finished
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS write finished
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS read server session ticket
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS read change cipher spec
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: SSL_connect:SSLv3/TLS read finished
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: Matched subjectAltName: smtp.gmail.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465 CommonName smtp.gmail.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: subject_CN=smtp.gmail.com, issuer_CN=Google Internet Authority G3, fingerprint=DF:CB:AA:81:ED:77:D4:BE:E5:47:6F:0E:A3:44:99:BA, pkey_fingerprint=EE:0F:3A:CC:6E:4E:EB:C0:1D:88:B6:73:BD:42:C4:83
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: Verified TLS connection established to smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[10967]: DE8FE11C64A1: to=&amp;lt;them@their.domain&amp;gt;, relay=smtp.gmail.com[2607:f8b0:4001:c03::6c]:465, delay=1.3, delays=0.03/0.03/0.52/0.75, dsn=2.0.0, status=sent (250 2.0.0 OK  1556668753 y199sm14693187iof.88 - gsmtp)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/qmgr[10948]: DE8FE11C64A1: removed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can see that the TLS connection was made, negotiated over IPv6 with TLS 1.2 and a nice modern cipher, and the certificate matches the hostname we used. Then the email was sent.&lt;/p&gt;
&lt;p&gt;Google has some of their SMTP servers offering the newer, unfinalized TLS 1.3 protocol, since I see that about half the time:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;postfix/smtp[13812]: Verified TLS connection established to smtp.gmail.com[2607:f8b0:4001:c06::6d]:465: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And I received my test mail on the other end.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Now I can use any local mail client without having to separately configure each one to send outgoing mail to Gmail, and without having to enter the Gmail app password each time I start my mail client.&lt;/p&gt;
&lt;p&gt;Sending email is immediate since the client only waits for the fast local queueing to complete, and Postfix forwards the mail in the background.&lt;/p&gt;
&lt;p&gt;Remember to check your logs to make sure your mail is getting delivered, since your client will no longer know if it is not. If nobody replies to you for too many hours, check the logs first! Or set up something to monitor your logs for errors and alert you.&lt;/p&gt;
&lt;p&gt;This approach is also useful on servers that need to send application email from a Gmail account, such as when you want mail to have a From: header with a @gmail.com or G Suite domain address.&lt;/p&gt;
&lt;p&gt;Thanks, open source community, for the good software and documentation and open standards!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Reach customers and drive sales with MailChimp</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2016/08/reach-customers-and-drive-sales-with/"/>
      <id>https://www.endpointdev.com/blog/2016/08/reach-customers-and-drive-sales-with/</id>
      <published>2016-08-22T00:00:00+00:00</published>
      <author>
        <name>Josh Lavin</name>
      </author>
      <content type="html">
        &lt;p&gt;It’s a good idea for ecommerce stores to regularly contact their customers. This not only reminds customers that your business exists, but also allows the sharing of new products and resources that can enrich the lives of your customers and clients. One of the easiest ways to stay in touch is by using an email newsletter service, such as &lt;a href=&#34;http://mailchimp.com/&#34;&gt;MailChimp&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img alt=&amp;quot;&amp;quot; border=&amp;quot;0&amp;quot; height=&amp;quot;200&amp;quot; src=&amp;quot;/blog/2016/08/reach-customers-and-drive-sales-with/image-0.png&amp;quot; title=&amp;quot;Freddie, the MailChimp mascot&amp;quot; width=&amp;quot;191&amp;quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MailChimp offers the regular suite of email newsletter services: lists, campaigns, and reports—​but in addition, they allow an ecommerce store to &lt;a href=&#34;http://mailchimp.com/connect-your-store/&#34;&gt;integrate sales data back into MailChimp&lt;/a&gt;. When you have &lt;strong&gt;detailed shopping statistics&lt;/strong&gt; for each subscriber, it opens new possibilities for customized marketing campaigns.&lt;/p&gt;
&lt;h3 id=&#34;endless-possibilities&#34;&gt;Endless possibilities&lt;/h3&gt;
&lt;p&gt;For example, imagine you have an email mailing list with 1,000 recipients. Instead of mailing the same generic newsletter to each subscriber, what if you could segment the list to identify your 100 best customers, and email them a special campaign?&lt;/p&gt;
&lt;p&gt;Additional ideas could include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reach out to inactive subscribers, offering a coupon&lt;/li&gt;
&lt;li&gt;Invite your best customers to a secret sale&lt;/li&gt;
&lt;li&gt;Re-engage customers who placed items in their cart, but left without purchasing&lt;/li&gt;
&lt;li&gt;Offer complementary products to purchasers of &lt;em&gt;Product X&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;automatic-marketing&#34;&gt;Automatic marketing&lt;/h3&gt;
&lt;p&gt;Once your store has sales data for subscribers, and you’ve decided on the campaigns you want to run with this data, the next step is to &lt;strong&gt;automate the process&lt;/strong&gt;. This is where MailChimp’s &lt;a href=&#34;http://mailchimp.com/features/automation/&#34;&gt;Automation&lt;/a&gt; feature comes in. Spend some time up-front to craft the automated campaigns, then sit back and let MailChimp run them for you; day in, day out.&lt;/p&gt;
&lt;h3 id=&#34;steps-to-implement&#34;&gt;Steps to implement&lt;/h3&gt;
&lt;p&gt;There are several off-the-shelf &lt;a href=&#34;https://connect.mailchimp.com/collections/e-commerce&#34;&gt;integrations for ecommerce stores&lt;/a&gt;, including Magento and BigCommerce.&lt;/p&gt;
&lt;p&gt;Users of Perl and &lt;a href=&#34;/expertise/perl-interchange/&#34;&gt;Interchange&lt;/a&gt; can use our newly-released toolsets of the &lt;a href=&#34;http://p3rl.org/Mail::Chimp3&#34;&gt;Mail::Chimp3 CPAN module&lt;/a&gt; and the &lt;a href=&#34;https://github.com/jdigory/interchange-extras/tree/master/mailchimp&#34;&gt;integration for Interchange5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/contact/&#34;&gt;Contact us&lt;/a&gt; today for expert help integrating one of these solutions with your ecommerce store.&lt;/p&gt;
&lt;h3 id=&#34;go-beyond-the-simple-newsletter&#34;&gt;Go beyond the simple newsletter&lt;/h3&gt;
&lt;p&gt;Most businesses already have an email newsletter. Hopefully, you are sending regular email campaigns with it. This is a great first step. Going beyond this to segment your email list and reach out to these segments with relevant information to each of them, is the next step. Not only can this &lt;strong&gt;increase your sales&lt;/strong&gt;, but it also &lt;strong&gt;respects your clients’ and customers’ time and preferences&lt;/strong&gt;. It’s a win-win for all.&lt;/p&gt;
&lt;p&gt;Additional resource: &lt;a href=&#34;http://mailchimp.com/resources/guides/mailchimp-for-online-sellers/&#34;&gt;MailChimp for Online Sellers&lt;/a&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Taking control of your IMAP mail with IMAPFilter</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/11/taking-control-of-your-imap-mail-with/"/>
      <id>https://www.endpointdev.com/blog/2015/11/taking-control-of-your-imap-mail-with/</id>
      <published>2015-11-06T00:00:00+00:00</published>
      <author>
        <name>Patrick Lewis</name>
      </author>
      <content type="html">
        &lt;p&gt;Organizing and dealing with incoming email can be tedious, but with &lt;a href=&#34;https://github.com/lefcha/imapfilter&#34;&gt;IMAPFilter&lt;/a&gt;’s simple configuration syntax you can automate any action that you might want to perform on an email and focus your attention on the messages that are most important to you.&lt;/p&gt;
&lt;p&gt;Most desktop and mobile email clients include support for rules or filters to deal with incoming mail messages but I was interested in finding a client-agnostic solution that could run in the background, processing incoming messages before they ever reached my phone, tablet or laptop. Configuring a set of rules in a desktop email client isn’t as useful when you might also be checking your mail from a web interface or mobile client; either you need to leave your desktop client running 24/7 or end up with an unfiltered mailbox on your other devices.&lt;/p&gt;
&lt;p&gt;I’ve configured IMAPFilter to run on my home Linux server and it’s doing a great job of processing my incoming mail, automatically sorting things like newsletters and automated Git commit messages into separate mailboxes and reserving my inbox for higher priority incoming mail.&lt;/p&gt;
&lt;p&gt;IMAPFilter is available in most package managers and easily configured with a single ~/.imapfilter/config.lua file. A helpful &lt;a href=&#34;https://github.com/lefcha/imapfilter/blob/master/samples/config.lua&#34;&gt;example config.lua&lt;/a&gt; is available in IMAPFilter’s GitHub repository and is what I used as the basis for my personal configuration.&lt;/p&gt;
&lt;p&gt;A few of my favorite IMAPFilter rules (where “endpoint” is configured as my work IMAP account):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #75715e&amp;#34;&lt;/span&gt;&amp;gt;-- Mark daily timesheet reports as read, move them into a Timesheets archive mailbox&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;timesheets&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt; &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f92672&amp;#34;&lt;/span&gt;&amp;gt;=&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt; &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;endpoint[&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;INBOX&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;]:contain_from(&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;timesheet@example.com&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;)&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;timesheets:mark_seen()&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;timesheets:move_messages(endpoint[&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;Archive/Timesheets&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;])&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #75715e&amp;#34;&lt;/span&gt;&amp;gt;-- Sort newsletters into newsletter-specific mailboxes&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;jsweekly&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt; &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f92672&amp;#34;&lt;/span&gt;&amp;gt;=&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt; &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;endpoint[&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;INBOX&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;]:contain_from(&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;jsw@peterc.org&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;)&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;jsweekly:move_messages(endpoint[&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;Newsletters/JavaScript Weekly&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;])&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;hn&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt; &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f92672&amp;#34;&lt;/span&gt;&amp;gt;=&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt; &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;endpoint[&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;INBOX&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;]:contain_from(&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;kale@hackernewsletter.com&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;)&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;hn:move_messages(endpoint[&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #e6db74&amp;#34;&lt;/span&gt;&amp;gt;&amp;#39;Newsletters/Hacker Newsletter&amp;#39;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;style&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;color: #f8f8f2&amp;#34;&lt;/span&gt;&amp;gt;])&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;span&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that IMAPFilter will create missing mailboxes when running “move_messages”, so you don’t need to set those up ahead of time. These are basic examples but the sample config.lua is a good source of other filter ideas, including combining messages matching multiple criteria into a single result set.&lt;/p&gt;
&lt;p&gt;In addition to these basic rules, IMAPFilter also supports more advanced configurations including the ability to perform actions on messages based on the results of passing their content through an external command. This opens up possibilities like performing your own local spam filtering by sending each message through SpamAssassin and moving messages into spam mailboxes based on the exit codes returned by spamc. As of this writing I’m still in the process of training SpamAssassin to reliably recognize spam vs. ham but hope to integrate its spam detection into my own IMAPFilter configuration soon.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Postfix Address Verification</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/05/postfix-address-verification/"/>
      <id>https://www.endpointdev.com/blog/2015/05/postfix-address-verification/</id>
      <published>2015-05-28T00:00:00+00:00</published>
      <author>
        <name>Marco Matarazzo</name>
      </author>
      <content type="html">
        &lt;p&gt;We recently upgraded some mail servers, moving from Exim to Postfix in the process. These server works as a front line spam/RBL filter, rejecting invalid message and relaying valid ones to different SMTP based on the destination domain.&lt;/p&gt;
&lt;p&gt;While looking for the best configuration layout to achieve this, we found that Postfix has a very useful and interesting feature: Address Verification. This technique allows the Postfix server to check that a sender or a recipient address is valid before accepting a message, preventing junk messages from entering the queue.&lt;/p&gt;
&lt;h3 id=&#34;how-does-address-verification-work&#34;&gt;How does Address Verification work?&lt;/h3&gt;
&lt;p&gt;Upon receiving a message Postfix will probe the preferred MTA for the address. If that address is valid the message is accepted and processed, otherwise it is rejected.&lt;/p&gt;
&lt;p&gt;Message Probes does not actually go through the whole delivery process; Postfix will just connect to the MTA, send a HELO + MAIL FROM + RCPT TO sequence and check its response. Probe checks results are cached on disk, minimizing network and resource impact. During this check the client is put “on hold”; if the probe takes too much a temporary reject is given; a legitimate mail server will have no problem retrying the delivery later, when the cached result will likely be available.&lt;/p&gt;
&lt;p&gt;Everything is highly configurable: response codes, timeouts, cache storage type and location, and so on.&lt;/p&gt;
&lt;h3 id=&#34;configure-recipient-address-verification&#34;&gt;Configure Recipient Address Verification&lt;/h3&gt;
&lt;p&gt;In our case, we wanted to only accept messages with a valid recipient address. Recipient Address Verification took care of this in a very smooth and elegant way.&lt;/p&gt;
&lt;p&gt;Adding Recipient Address Verification it’s easy. Just add these lines to /etc/postfix/main.cf:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Your relaying configuration will already be in place. For example:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# relayhost = [next.hop.ip.address]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;smtpd_recipient_restrictions = 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    permit_mynetworks
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    reject_unauth_destination
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    reject_unknown_recipient_domain
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    reject_unverified_recipient
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Custom reply message when probe fails (Postfix 2.6 and later)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;unverified_recipient_reject_reason = Address lookup failure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Settings order is important as they are verified one after another; when a decision is triggered (PERMIT or REJECT) the parsing process ends.&lt;/p&gt;
&lt;p&gt;Let’s see them in details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;permit_mynetworks&lt;/strong&gt;: &lt;em&gt;permit&lt;/em&gt; message from local or trusted addresses listed in $mynetworks;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;reject_unauth_destination&lt;/strong&gt;: &lt;em&gt;reject&lt;/em&gt; message unless recipient domain is a local one (typically in $mydestination, $virtual_alias_domains or $virtual_mailbox_domains) or is accepted for forwarding (in $relay_domains);&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;reject_unknown_recipient_domain&lt;/strong&gt;: &lt;em&gt;reject&lt;/em&gt; message if recipient domain has no DNS MX and A record, or has a malformed MX record;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;reject_unverified_recipient&lt;/strong&gt;: &lt;em&gt;reject&lt;/em&gt; the message if the Recipient Address Verification fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to learn more, the best place to find more information is the &lt;a href=&#34;http://www.postfix.org/ADDRESS_VERIFICATION_README.html&#34;&gt;Postfix Address Verification Howto&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Parsing Email Addresses in Rails with Mail::Address</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/10/parsing-email-addresses-in-rails/"/>
      <id>https://www.endpointdev.com/blog/2014/10/parsing-email-addresses-in-rails/</id>
      <published>2014-10-03T00:00:00+00:00</published>
      <author>
        <name>Patrick Lewis</name>
      </author>
      <content type="html">
        &lt;p&gt;I&amp;rsquo;ve recently discovered the Mail::Address class and have started using it for storing and working with email addresses in Rails applications. Working with an email address as an Address object rather than a String makes it easy to retrieve different parts of the address and I recommend trying it out if you&amp;rsquo;re dealing with email addresses in your application.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/mikel/mail&#34;&gt;Mail&lt;/a&gt; is a Ruby library that handles email generation, parsing, and sending. Rails&amp;rsquo; own ActionMailer module is dependent on the Mail gem, so you&amp;rsquo;ll find that Mail has already been included as part of your Rails application installations and is ready for use without any additional installation or configuration.&lt;/p&gt;
&lt;p&gt;The Mail::Address class within the library can be used in Rails applications to provide convenient, object-oriented ways of working with email addresses.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;http://rdoc.info/github/mikel/mail/Mail/Address&#34;&gt;class documentation&lt;/a&gt; provides some of the highlights:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Address&lt;/span&gt;.new(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Patrick Lewis (My email address) &amp;lt;patrick@example.endpoint.com&amp;gt;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.format       &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; &amp;#39;Patrick Lewis &amp;lt;patrick@example.endpoint.com&amp;gt; (My email address)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.address      &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; &amp;#39;patrick@example.endpoint.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.display_name &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; &amp;#39;Patrick Lewis&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.local        &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; &amp;#39;patrick&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.domain       &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; &amp;#39;example.endpoint.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.comments     &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; [&amp;#39;My email address&amp;#39;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.to_s         &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; &amp;#39;Patrick Lewis &amp;lt;patrick@example.endpoint.com&amp;gt; (My email address)&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Mail::Address makes it trivial to extract the username, domain name, or just about any other component part of an email address string. Also, its #format and #to_s methods allow you to easily return the full address as needed without having to recombine things yourself.&lt;/p&gt;
&lt;p&gt;You can also build a Mail::Address object by assigning email and display name strings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a = &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Address&lt;/span&gt;.new
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.address = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;patrick@example.endpoint.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.display_name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Patrick Lewis&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; #&amp;lt;Mail::Address:69846669408060 Address: |Patrick Lewis &amp;lt;patrick@example.endpoint.com&amp;gt;| &amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.display_name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Patrick J. Lewis&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; #&amp;lt;Mail::Address:69846669408060 Address: |&amp;#34;Patrick J. Lewis&amp;#34; &amp;lt;patrick@example.endpoint.com&amp;gt;| &amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;a.domain &lt;span style=&#34;color:#888&#34;&gt;#=&amp;gt; &amp;#34;example.endpoint.com&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This provides an easy, reliable way to generate Mail::Address objects that catches input errors if the supplied address or display name are not parseable.&lt;/p&gt;
&lt;p&gt;I encourage anyone who&amp;rsquo;s manipulating email addresses in their Rails applications to try using this class. I&amp;rsquo;ve found it especially useful for defining application-wide constants for the &amp;lsquo;From&amp;rsquo; addresses in my mailers; by creating them as Mail::Address objects I can access their full strings with display names and addresses in my mailers, but also grab just the email addresses themselves for obfuscation or other display purposes in my views.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Postfix IPv6 preference</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/08/postfix-ipv6-preference/"/>
      <id>https://www.endpointdev.com/blog/2014/08/postfix-ipv6-preference/</id>
      <published>2014-08-19T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;On a Debian GNU/Linux 7 (“wheezy”) system with both IPv6 and IPv4 networking setup, running Postfix 2.9.6 as an SMTP server, we ran into a mildly perplexing situation. The mail logs showed that outgoing mail to MX servers we know have IPv6 addresses, the IPv6 address was only being used occasionally, while the IPv4 address was being used often. We expected it to always use IPv6 unless there was some problem, and that’s been our experience on other mail servers.&lt;/p&gt;
&lt;p&gt;At first we suspected some kind of flaky IPv6 setup on this host, but that turned out not to be the case. The MX servers themselves are fine using only IPv6. In the end, it turned out to be a Postfix configuration option called &lt;a href=&#34;http://www.postfix.org/postconf.5.html#smtp_address_preference&#34;&gt;smtp_address_preference&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;smtp_address_preference (default: any)&lt;/p&gt;
&lt;p&gt;The address type (“ipv6”, “ipv4” or “any”) that the Postfix SMTP client will try first, when a destination has IPv6 and IPv4 addresses with equal MX preference. This feature has no effect unless the inet_protocols setting enables both IPv4 and IPv6. With Postfix 2.8 the default is “ipv6”.&lt;/p&gt;
&lt;p&gt;Notes for mail delivery between sites that have both IPv4 and IPv6 connectivity:&lt;/p&gt;
&lt;p&gt;The setting “smtp_address_preference = ipv6” is unsafe. It can fail to deliver mail when there is an outage that affects IPv6, while the destination is still reachable over IPv4.&lt;/p&gt;
&lt;p&gt;The setting “smtp_address_preference = any” is safe. With this, mail will eventually be delivered even if there is an outage that affects IPv6 or IPv4, as long as it does not affect both.&lt;/p&gt;
&lt;p&gt;This feature is available in Postfix 2.8 and later.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;That documentation made it sound as if the default had changed to “ipv6” in Postfix 2.8, but at least on Debian 7 with Postfix 2.9, it was still defaulting to “any”, thus effectively randomly choosing between IPv4 and IPv6 on outbound SMTP connections where the MX record pointed to both.&lt;/p&gt;
&lt;p&gt;Changing the option to “ipv6” made Postfix behave as expected.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Unable to Bcc in mail, Spree 2.0 Stable Rails 3.2.14</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/06/unable-to-bcc-in-mail-spree-20-stable/"/>
      <id>https://www.endpointdev.com/blog/2014/06/unable-to-bcc-in-mail-spree-20-stable/</id>
      <published>2014-06-09T00:00:00+00:00</published>
      <author>
        <name>Matt Galvin</name>
      </author>
      <content type="html">
        &lt;p&gt;Hello again all.  As usual, I was working on a &lt;a href=&#34;https://spreecommerce.org/&#34;&gt;Spree Commerce&lt;/a&gt; website.  I recently encountered an issue when trying to bcc order confirmation emails.  Others have been asking about this on &lt;a href=&#34;https://github.com/spree/spree/issues/4484&#34;&gt;Github&lt;/a&gt; and also on the &lt;a href=&#34;https://groups.google.com/forum/#!msg/spree-user/50yjID6znOE/QSr51V1xgrUJ&#34;&gt;Spree mailing list&lt;/a&gt;, so it was time to write about the problem and the solution that worked for me.&lt;/p&gt;
&lt;p&gt;First, I’d like to briefly describe the use case here.  As with any typical e-commerce site, a user visits the site, adds some items to their cart, and checks out.  After which, an order confirmation (order summary) email is sent to the user with their order details and any extra information provided by the seller.&lt;/p&gt;
&lt;p&gt;Spree pretty much handles all this for you automatically.  What about if you as the business owner would like a copy of this e-mail?  Easy enough.  If you review the &lt;a href=&#34;https://web.archive.org/web/20160606002703/http://guides.spreecommerce.org/user/configuring_mail_methods.html&#34;&gt;Spree documentation&lt;/a&gt; you will see simple instructions for the  “Mail Method Settings” to set up in the Spree Admin Interface.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/06/unable-to-bcc-in-mail-spree-20-stable/image-0.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/06/unable-to-bcc-in-mail-spree-20-stable/image-0.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Ok, so let’s say you follow all the instructions and start placing test orders (or receiving real ones), and you’re not getting bcc’d?  This is where it gets tricky, so let’s check out a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Check the logs&lt;/p&gt;
&lt;p&gt;Check to see if Spree/Rails is attempting to send the confirmation email to your Bcc recipient&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Try development, test, &amp;amp; production modes&lt;/p&gt;
&lt;p&gt;If the Bcc email is not getting sent, keep following along for the solution&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure the interceptor works by adding a value into “INTERCEPT EMAIL ADDRESS” via the admin.  If emails are being intercepted you know the the interceptor is working.  Why is that important? Because, that is also where the bcc code is.&lt;/p&gt;
&lt;p&gt;core/lib/spree/mail_interceptor.rb&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;module Spree
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  module Core
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    class MailInterceptor
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      def self.delivering_email(message)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        return unless MailSettings.override?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        if Config[:intercept_email].present?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          message.subject = &amp;#34;#{message.to} #{message.subject}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          message.to = Config[:intercept_email]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        if Config[:mail_bcc].present?
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          message.bcc ||= Config[:mail_bcc]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can ensure that more than one email can be sent by updating &lt;code&gt;mail(to: @order.email, from: from_address, subject: subject)&lt;/code&gt; to be an array of emails, like &lt;code&gt;mail(to: [@order.email, &amp;quot;some_other_email@somewhere.com&amp;quot;], from: from_address, subject: subject)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;core/app/mailers/spree/order_mailer.rb&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;module&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Spree&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;OrderMailer&lt;/span&gt; &amp;lt; &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;BaseMailer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;confirm_email&lt;/span&gt;(order, resend = &lt;span style=&#34;color:#080&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#33b&#34;&gt;@order&lt;/span&gt; = order.respond_to?(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;) ? order : &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Order&lt;/span&gt;.find(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      subject = (resend ? &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;[&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;.t(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:resend&lt;/span&gt;).upcase&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;] &amp;#34;&lt;/span&gt; : &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      subject += &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Config&lt;/span&gt;[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:site_name&lt;/span&gt;]&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;.t(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;order_mailer.confirm_email.subject&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; #&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#33b&#34;&gt;@order&lt;/span&gt;.number&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      mail(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color:#33b&#34;&gt;@order&lt;/span&gt;.email, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;from&lt;/span&gt;: from_address, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;subject&lt;/span&gt;: subject)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;cancel_email&lt;/span&gt;(order, resend = &lt;span style=&#34;color:#080&#34;&gt;false&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#33b&#34;&gt;@order&lt;/span&gt; = order.respond_to?(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:id&lt;/span&gt;) ? order : &lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Order&lt;/span&gt;.find(order)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      subject = (resend ? &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;[&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;.t(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:resend&lt;/span&gt;).upcase&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;] &amp;#34;&lt;/span&gt; : &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      subject += &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;::&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Config&lt;/span&gt;[&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;:site_name&lt;/span&gt;]&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#036;font-weight:bold&#34;&gt;Spree&lt;/span&gt;.t(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;order_mailer.cancel_email.subject&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; #&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#33b&#34;&gt;@order&lt;/span&gt;.number&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      mail(&lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color:#33b&#34;&gt;@order&lt;/span&gt;.email, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;from&lt;/span&gt;: from_address, &lt;span style=&#34;color:#a60;background-color:#fff0f0&#34;&gt;subject&lt;/span&gt;: subject)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At this point, you’ve verified the interceptor works, and the mailer can definitely send more than one email.  Here is the final piece that is not mentioned anywhere on the Spree site, or any resolutions for any posts I had found: you may need to contact your hosting provider and ask them to make any necessary adjustments to allow for the bcc messages.  I saw that several people have gotten hung up on this and I hope this post has saved you some time.  If you are still having trouble, please feel free to reach out in the comments.  You can refer to the &lt;a href=&#34;https://github.com/spree/spree/issues/4484&#34;&gt;Spree issue&lt;/a&gt; I created on this topic some time ago.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>SPF, DKIM and DMARC brief explanation and best practices</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/04/spf-dkim-and-dmarc-brief-explanation/"/>
      <id>https://www.endpointdev.com/blog/2014/04/spf-dkim-and-dmarc-brief-explanation/</id>
      <published>2014-04-15T00:00:00+00:00</published>
      <author>
        <name>Emanuele “Lele” Calò</name>
      </author>
      <content type="html">
        &lt;p&gt;Spam mail messages have been a plague since the Internet became popular and they kept growing more and more as the number of devices and people connected grew. Despite the numerous attempts of creation of anti-spam tools, there’s still a fairly high number of unwanted messages sent every day.&lt;/p&gt;
&lt;p&gt;Luckily it seems that lately something is changing with the adoption of three (relatively) new tools which are starting to be widely used: SPF, DKIM and DMARC. Let’s have a quick look at each of these tools and what they achieve.&lt;/p&gt;
&lt;h3 id=&#34;what-are-spf-dkim-and-dmarc&#34;&gt;What are SPF, DKIM and DMARC&lt;/h3&gt;
&lt;p&gt;SPF (Sender Policy Framework) is a DNS text entry which shows a list of servers that should be considered allowed to send mail for a specific domain. Incidentally the fact that SPF is a DNS entry can also considered a way to enforce the fact that the list is authoritative for the domain, since the owners/administrators are the only people allowed to add/change that main domain zone.&lt;/p&gt;
&lt;p&gt;DKIM (DomainKeys Identified Mail) should be instead considered a method to verify that the messages’ &lt;strong&gt;content&lt;/strong&gt; are trustworthy, meaning that they weren’t changed from the moment the message left the initial mail server. This additional layer of trustability is achieved by an implementation of the standard public/private key signing process. Once again the owners of the domain add a DNS entry with the &lt;strong&gt;public DKIM key&lt;/strong&gt; which will be used by receivers to verify that the message DKIM signature is correct, while on the sender side the server will sign the entitled mail messages with the corresponding private key.&lt;/p&gt;
&lt;p&gt;DMARC (Domain-based Message Authentication, Reporting and Conformance) empowers SPF and DKIM by stating a clear policy which should be used about both the aforementioned tools and allows to set an address which can be used to send reports about the mail messages statistics gathered by receivers against the specific domain [1].&lt;/p&gt;
&lt;h3 id=&#34;how-do-they-work&#34;&gt;How do they work?&lt;/h3&gt;
&lt;p&gt;All these tools relies heavily on DNS and luckily their functioning process, after all the setup phase is finished, is simple enough to be (roughly) explained below:&lt;/p&gt;
&lt;h4 id=&#34;spf&#34;&gt;SPF&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;upon receipt the HELO message and the sender address are fetched by the receiving mail server&lt;/li&gt;
&lt;li&gt;the receiving mail server runs an TXT DNS query against the claimed domain SPF entry&lt;/li&gt;
&lt;li&gt;the SPF entry data is then used to verify the sender server&lt;/li&gt;
&lt;li&gt;in case the check fails a rejection message is given to the sender server&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/04/spf-dkim-and-dmarc-brief-explanation/image-0-big.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/04/spf-dkim-and-dmarc-brief-explanation/image-0.png&#34;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;
&lt;span style=&#34;font-weight: lighter; size: 0.25em;&#34;&gt;Image by Ale2006-from-en, licensed under &lt;a href=&#34;https://creativecommons.org/licenses/by-sa/3.0/deed.en&#34;&gt;CC BY-SA 3.0&lt;/a&gt; from &lt;a href=&#34;https://en.wikipedia.org/wiki/E-mail_authentication&#34;&gt;Wikipedia&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;h4 id=&#34;dkim&#34;&gt;DKIM&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;when sending an outgoing message, the last server within the domain infrastructure checks against its internal settings if the domain used in the “From:” header is included in its “signing table”. If not the process stops here&lt;/li&gt;
&lt;li&gt;a new header, called “DKIM-Signature”, is added to the mail message by using the private part of the key on the message content&lt;/li&gt;
&lt;li&gt;from here on the message &lt;em&gt;main&lt;/em&gt; content cannot be modified otherwise the DKIM header won’t match anymore&lt;/li&gt;
&lt;li&gt;upon reception the receiving server will make a TXT DNS query to retrieve the key used in the DKIM-Signature field&lt;/li&gt;
&lt;li&gt;the DKIM header check result can be then used when deciding if a message is fraudulent or trustworthy&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;http://2.bp.blogspot.com/-eQ123eQEqB4/U0yIgEIXf_I/AAAAAAAAAS8/Kbwz5xMrP4Q/s1600/DomainKeys_Identified_Mail_(DKIM).png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/04/spf-dkim-and-dmarc-brief-explanation/image-1.png&#34;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;
&lt;span style=&#34;font-weight: lighter; size: 0.25em;&#34;&gt;Image by Ludovic Rembert of &lt;a href=&#34;https://privacycanada.net&#34;&gt;PrivacyCanada.net&lt;/a&gt;, licensed under &lt;a href=&#34;https://creativecommons.org/licenses/by-sa/3.0/deed.en&#34;&gt;CC BY-SA 3.0&lt;/a&gt;, cited in &lt;a href=&#34;https://en.wikipedia.org/wiki/E-mail_authentication&#34;&gt;Wikipedia&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;h4 id=&#34;dmarc&#34;&gt;DMARC&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;upon reception the receiving mail server checks if there is any existing DMARC policy published in the domain used by the SPF and/or DKIM checks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if &lt;em&gt;one or both&lt;/em&gt; the SPF and DKIM checks succeed while still being &lt;em&gt;aligned&lt;/em&gt; with the policy set by DMARC, then the check is considered successful, otherwise it’s set as failed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if the check fails, based on the action published by the DMARC policy, different actions are taken&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2014/04/spf-dkim-and-dmarc-brief-explanation/image-2-big.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2014/04/spf-dkim-and-dmarc-brief-explanation/image-2.jpeg&#34;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;
&lt;span style=&#34;font-weight: lighter; size: 0.25em;&#34;&gt;Source &lt;a href=&#34;https://dmarc.org/overview.html&#34;&gt;DMARC.org&lt;/a&gt;, licensed under &lt;a href=&#34;https://creativecommons.org/licenses/by/3.0/&#34;&gt;CC BY 3.0&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;h3 id=&#34;the-bad-news-limits-and-best-practices&#34;&gt;The bad news: limits and best practices&lt;/h3&gt;
&lt;p&gt;Unfortunately even by having a perfectly functional mail system with all the above tools enforced you won’t be 100% safe from the bad guys out there. Not all servers are using all three tools shown above. It’s enough to take a look at the table shown in Wikipedia [2] to see how that’s possible.&lt;/p&gt;
&lt;p&gt;Furthermore there are some limits that you should always consider when dealing with SPF, DKIM and DMARC:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;as already said above DKIM alone doesn’t grant in any way that the sender server is allowed to send outgoing mail for the specific domain&lt;/li&gt;
&lt;li&gt;SPF is powerless with messages forged in shared hosting scenario as all the mail will appear as the same coming IP&lt;/li&gt;
&lt;li&gt;DMARC is still in its early age and unfortunately not used as much as hoped to make a huge difference&lt;/li&gt;
&lt;li&gt;DMARC can (and will) break your mail flow if you don’t set up &lt;strong&gt;both&lt;/strong&gt; SPF and DKIM &lt;strong&gt;before&lt;/strong&gt; changing DMARC policy to anything above “none”.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please work through the proper process carefully, otherwise your precious messages won’t be delivered to your users as potentially seen as fraudulent by a wrong SPF, DKIM or DMARC setup.&lt;/p&gt;
&lt;h3 id=&#34;whats-the-message-behind-all-this-should-i-use-these-tools-or-not&#34;&gt;What’s the message behind all this? Should I use these tools or not?&lt;/h3&gt;
&lt;p&gt;The short answer is: Yes. The longer answer is that everybody should and eventually will in future, but we’re just not there yet. So even if all these tools already have a lot of power, they’re not still shining as bright as they should because of poor adoption.&lt;/p&gt;
&lt;p&gt;Hopefully things will change soon and that starts by every one of us adopting these tools as soon as possible.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;[1] The lack of such a monitoring tool is considered one of the reasons why other tools (such as ADSP) in past have failed during the adoption phase.&lt;/p&gt;
&lt;p&gt;[2] &lt;a href=&#34;https://en.wikipedia.org/wiki/Comparison_of_mail_servers&#34;&gt;Comparison of mail servers on Wikipedia&lt;/a&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Restrict IMAP account access to one (or more) IP address</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2014/03/restrict-imap-account-access-to-one-or/"/>
      <id>https://www.endpointdev.com/blog/2014/03/restrict-imap-account-access-to-one-or/</id>
      <published>2014-03-13T00:00:00+00:00</published>
      <author>
        <name>Emanuele “Lele” Calò</name>
      </author>
      <content type="html">
        &lt;p&gt;If you’re in need of some extra layer of security on your mail server and know in advance who is going to access your IMAP account and from where (meaning which IP), then the following trick could be the perfect solution for you.&lt;/p&gt;
&lt;p&gt;In order to use this feature you’ll have to use &lt;em&gt;Dovecot 2.x+&lt;/em&gt; and then just add a comma separated list of addresses/subnets to the last field of your dovecot passwd auth file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;user:{plain}password::::::allow_nets=192.168.0.0/24,10.0.0.1,2001:abcd:abcd::0:0/80&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After a quick reload Dovecot will start to enforce the specified new settings.&lt;/p&gt;
&lt;p&gt;An additional neat aspect is that from an attacker perspective the given error will always be the same one got from a “wrong password” attempt, making basically impossible to discover this further protection.&lt;/p&gt;
&lt;p&gt;Stay safe out there!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>An update to the email_verifier gem has been released</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2013/12/an-update-to-emailverifier-gem-has-been/"/>
      <id>https://www.endpointdev.com/blog/2013/12/an-update-to-emailverifier-gem-has-been/</id>
      <published>2013-12-04T00:00:00+00:00</published>
      <author>
        <name>Kamil Ciemniewski</name>
      </author>
      <content type="html">
        &lt;p&gt;I have just released a newer version of the email_verifier gem. It now supports Rails localization out of the box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you are one of gem’s users—​you can safely update your Bundles!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With this release, the project has also gained its first contributors. Some time ago, one of gem’s users—​Franscesco Gnarra has asked me about a possibility of having validation messages localized. While I was planning to do it myself—​Francesco took an action and contributed to the project himself.&lt;/p&gt;
&lt;p&gt;The project also received a pull request from another Rails developer—​Maciej Walusiak. His commit provided a way to handle Dnsruby::NXDomain exceptions.&lt;/p&gt;
&lt;p&gt;It’s great to see that our work is helpful for others. If you’d like to contribute yourself—​I invite you to do so.&lt;/p&gt;
&lt;p&gt;Email verifier at GitHub: &lt;a href=&#34;https://github.com/kamilc/email_verifier&#34;&gt;https://github.com/kamilc/email_verifier&lt;/a&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Using Gmail at Work</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/12/using-gmail-at-work/"/>
      <id>https://www.endpointdev.com/blog/2011/12/using-gmail-at-work/</id>
      <published>2011-12-15T00:00:00+00:00</published>
      <author>
        <name>Brian Buchalter</name>
      </author>
      <content type="html">
        &lt;h3 id=&#34;the-short-story&#34;&gt;The Short Story&lt;/h3&gt;
&lt;p&gt;For those who don’t care about why, just how&amp;hellip;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;a href=&#34;https://accounts.google.com/NewAccount?service=mail&amp;amp;continue=http://mail.google.com/mail/e-11-63fb73ea731526b75ac5a66a770b0-ee45c8c140b7a1c66d569e7562acee65c08f1c21&amp;amp;type=2&#34;&gt;new Gmail account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Setup &lt;a href=&#34;https://support.google.com/mail/bin/answer.py?hl=en&amp;amp;answer=21289&#34;&gt;Mail Fetcher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Setup &lt;a href=&#34;https://support.google.com/mail/bin/answer.py?hl=en&amp;amp;answer=22370&#34;&gt;send email from another account&lt;/a&gt;;and make it your default&lt;/li&gt;
&lt;li&gt;Verify you send and receive as your corporate address by default using the web client&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.google.com/gmail/about/&#34;&gt;Setup your mobile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;From your mobile go to &lt;a href=&#34;https://m.google.com/sync&#34;&gt;m.google.com/sync&lt;/a&gt; and Enable “Send Mail As” for this device (tested only on iOS)&lt;/li&gt;
&lt;li&gt;Verify you send and receive as your corporate address by default using your mobile client&lt;/li&gt;
&lt;li&gt;Setup Google Authorship with your domain’s email address&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;the-long-story&#34;&gt;The Long Story&lt;/h3&gt;
&lt;p&gt;Here at End Point there are a lot of opinions about email clients. Our hardcore folks like &lt;a href=&#34;https://en.wikipedia.org/wiki/Alpine_(email_client)&#34;&gt;Alpine&lt;/a&gt; while for most people Evolution, Thunderbird, or Outlook will do. As a Gmail user since September 2004, I found I needed to figure out how get our corporate email system to work with my preferred client.&lt;/p&gt;
&lt;p&gt;My first reaction was to have Gmail act as an IMAP client. I found (&lt;a href=&#34;https://productforums.google.com/forum/?hl=en#!category-topic/gmail/managing-settings-and-mail/b0v5HHlqbHs&#34;&gt;as many others had&lt;/a&gt;) that Gmail does not support IMAP integration with other accounts. However, Gmail does have a POP email client known as &lt;a href=&#34;https://support.google.com/mail/answer/21289?hl=en&#34;&gt;Mail Fetcher&lt;/a&gt;. I found that Gmail does support encrypted connections via POP, so use them if your email server supports them. When combined with the &lt;a href=&#34;https://gmail.googleblog.com/2010/01/default-https-access-for-gmail.html&#34;&gt;HTTPS by default&lt;/a&gt;, access to the Gmail web client seemed sufficiently secure.&lt;/p&gt;
&lt;p&gt;I now needed to send email not as my Gmail address, but as my End Point address. Google has well documented how to &lt;a href=&#34;https://support.google.com/mail/bin/answer.py?hl=en&amp;amp;answer=22370&#34;&gt;send email from another account&lt;/a&gt;. Again encrypted SMTP is supported and is strongly recommended. Also be sure to make your corporate email account the default account so you will always use your corporate email address and not the Gmail address.&lt;/p&gt;
&lt;p&gt;After verifying I was sending and receiving email properly, I needed to get my mobile setup. There are a &lt;a href=&#34;https://www.google.com/gmail/about/&#34;&gt;variety of options&lt;/a&gt; available for all the mobile platforms. On my iPhone, I had several other accounts already setup and found the native client to be acceptable. I decided I would configure the native iPhone email app to access Gmail, as well as Contacts and Calendar using Google’s support for Microsoft’s ActiveSync protocol, which Google has licensed and rebranded as &lt;a href=&#34;https://support.google.com/a/users/answer/138740?visit_id=1-636676970400046677-2511485164&amp;amp;hl=en&amp;amp;rd=1&#34;&gt;Google Sync&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had used Google Sync for other Exchange accounts at my previous job and found it worked very well. However, there are some &lt;a href=&#34;https://support.google.com/a/users/answer/139635?visit_id=1-636676970400046677-2511485164&amp;amp;hl=en&amp;amp;rd=1&#34;&gt;known issues&lt;/a&gt;, like not being able to accept event invitations recieved via POP. It’s worth checking these issues out to see if there are any blockers for you.&lt;/p&gt;
&lt;p&gt;After setting up “Google Sync” on my iPhone, I tested again, and found that by default, it would use my Gmail account as my default outgoing email account, despite the setting in the Gmail web client. I needed to use my corporate address here at End Point for sending mail from mobile; I thought I was sunk!&lt;/p&gt;
&lt;p&gt;Fortunately, it seems I over looked a section in the Google Sync setup documentation, labeled “Enable Send Mail As feature”. This feature solved my problem by having me go to &lt;a href=&#34;https://get.google.com/apptips/apps/?utm_source=googlemobile&amp;amp;utm_campaign=redirect#!/all&#34;&gt;m.google.com/sync&lt;/a&gt; from my iOS device and check Enable “Send Mail As” for this device. This would tell Google Sync to use the default outgoing account I had specified in the web client.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2011/12/using-gmail-at-work/image-0.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2011/12/using-gmail-at-work/image-0.png&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;/div&gt;
&lt;p&gt;One requirement here at End Point which this configuration does not meet is support for PGP encryption/decryption of messages. There is a &lt;a href=&#34;https://web.archive.org/web/20111203170252/http://gpg4browsers.recurity.com/&#34;&gt;Chrome plugin&lt;/a&gt; that claims to offer support, but as the authors from &lt;a href=&#34;http://www.theregister.co.uk/2011/11/23/browser_crypto_plugin_debuts/&#34;&gt;this post&lt;/a&gt; highlight:&lt;/p&gt;
&lt;p&gt;*There may also be resistance from crypto users – who already are a security-conscious lot – to trusting private keys and confidential messages to a set of PGP functions folded inside some JavaScript running inside a browser. *&lt;/p&gt;
&lt;p&gt;I’d have to say I agree. After following the instructions to install the plugin, I balked when it asked for my private key; I just didn’t feel comfortable. Despite this shortfall, most End Point email isn’t encrypted end-to-end. However, I can feel good knowing that my “last mile” connection to End Point’s servers are encrypted, end-to-end using encrypted POP, SMTP, and HTTPS.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Using the new version of imapfilter with mutt</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2011/10/imapfilter-lua-email-mutt-filtering/"/>
      <id>https://www.endpointdev.com/blog/2011/10/imapfilter-lua-email-mutt-filtering/</id>
      <published>2011-10-17T00:00:00+00:00</published>
      <author>
        <name>Greg Sabino Mullane</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;a href=&#34;/blog/2011/10/imapfilter-lua-email-mutt-filtering/image-0-big.jpeg&#34; onblur=&#34;try {parent.deselectBloggerImageGracefully();} catch(e) {}&#34;&gt;&lt;img alt=&#34;&#34; border=&#34;0&#34; id=&#34;BLOGGER_PHOTO_ID_5664298724633134418&#34; src=&#34;/blog/2011/10/imapfilter-lua-email-mutt-filtering/image-0.jpeg&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Image by Flickr user &lt;a href=&#34;https://www.flickr.com/photos/p886/&#34;&gt;p886&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My beloved &lt;a href=&#34;http://www.mutt.org/&#34;&gt;mutt&lt;/a&gt;/imapfilter combo recently stopped working after an operating system switch. (tl;dr: that combo rocks; use &lt;em&gt;ipairs&lt;/em&gt; instead of &lt;em&gt;pairs&lt;/em&gt;) When my laptop wireless stopped working, and after spending some time fighting with it, I decided to simply install a new OS. As all of my important data is on a separate partition, this was not that big a deal. I ended up using &lt;a href=&#34;https://www.scientificlinux.org/&#34;&gt;Scientific Linux&lt;/a&gt;, as I’d heard good things about it, and it was one of the few distros that actually would install on my laptop (failures for one reason or another: Fedora, FreeBSD, Ubuntu, and OpenBSD). After the install, I simply copied my ~/.mutt directory and ~/.muttrc file into place, and similarly copied my ~/.imapfilter directory, which contained the all important config.lua file. The &lt;a href=&#34;https://github.com/lefcha/imapfilter&#34;&gt;imapfilter program&lt;/a&gt; itself was not available via the normal &lt;a href=&#34;https://fedoraproject.org/wiki/Yum&#34;&gt;yum&lt;/a&gt; repositories, so I simply grabbed the latest and greatest and did a manual install:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git clone https://github.com/lefcha/imapfilter.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; imapfilter
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo yum install gcc lua-devel openssl-devel pcre-devel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ make
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo make install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I’ve used a lot of email clients over the years (and may have been using email longer than most people reading this). I started out (because that’s all there was) with non-graphical clients such as mail, pine, elm, and mutt. Over the years I also tried out many graphical clients, such as Evolution, Kmail, Eudora, Thunderbird, and Claws Mail. However, nothing ever worked quite right, so I eventually ended up back with mutt, and have been happy with it ever since. The one drawback (or strength) of mutt is its single-mindedness. It does email very well, but lets other tools handle the ancillary tasks. One of those tasks is filtering, and that’s where imapfilter comes in. I like to view all email that comes in, so mutt generally runs with my INBOX open. I scan through the items, marking them urgent if I need to keep them around, and deleting them if they are obvious trash. As needed, I’ll kick off a imapfilter run, which then puts all my read, non-urgent, non-deleted email into the appropriate &lt;a href=&#34;https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol&#34;&gt;IMAP folders&lt;/a&gt; for me (mutt is even smart enough to realize that the folder was externally changed by imapfilter).&lt;/p&gt;
&lt;p&gt;So I tried running imapfilter per usual on my new system and noticed an odd thing: each item in my filter was getting a minimum of 66 ‘hits’, even when there was not even 66 total emails in my inbox! I output the number of matches to each filter I use, so instead of seeing what I was usually did:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Mediawiki emails moved:    1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Backcountry emails moved:  10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Perl QA messages moved:    0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Wiki alerts deleted:       0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bucardo emails moved:      5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Maatkit emails moved:      0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Mail filtering complete&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I saw everything at N+66 instead:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Mediawiki emails moved:   67
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Backcountry emails moved: 76
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Perl QA messages moved:   66
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Wiki alerts deleted:      66
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Bucardo emails moved:     71
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Maatkit emails moved:     66
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Mail filtering complete&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Obviously, something was wonky. Glancing at the release notes showed that version 2.2 changed the format of the search results:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Since version 2.2, a different format is used for the returned structures of the searching methods, due to the introduction of multiple mailbox searching and meta-searching, and thus any configuration files that rely on them should be updated*&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;p&gt;Okay, but where was the 66 coming from? I created a ~/.imapfilter/test.lua file to show me exactly what was happening inside the loop over the results table. (imapfilter is written in a nice language called &lt;a href=&#34;https://www.lua.org/&#34;&gt;Lua&lt;/a&gt;, which calls its main data structures &lt;a href=&#34;https://www.lua.org/pil/2.5.html&#34;&gt;“tables”&lt;/a&gt;. Probably to the chagrin of those using Lua/database crossover tools like &lt;a href=&#34;https://github.com/pllua&#34;&gt;Pl/Lua&lt;/a&gt; :) The test.lua file looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;myaccount = IMAP {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  server   = &amp;#39;mail.example.com&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  username = &amp;#39;greg&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  password = &amp;#39;secret&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ssl      = &amp;#39;tls1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;inbox = myaccount[&amp;#39;INBOX&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;result = inbox:contain_subject(&amp;#39;bats&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;count = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;for k,v in pairs(result) do 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  count = count + 1 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  if count &amp;lt; 10 then
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(count, &amp;#34;Call to pairs:&amp;#34;,k,v)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(&amp;#34;Total count for pairs: &amp;#34; .. count);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;count = 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;for k,v in ipairs(result) do 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  count = count + 1 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  if count &amp;lt; 10 then
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(count, &amp;#34;Call to ipairs:&amp;#34;,k,v)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(&amp;#34;Total count for ipairs: &amp;#34; .. count);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I downloaded and compiled version 2.0 of imapfilter and ran the above code, knowing that there were exactly two emails in my inbox that had a subject containing the string ‘bats’:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[~/code/imapfilter-2.0] ./imapfilter -c ~/.imapfilter/test.lua
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;      Call to pairs:    &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;9&lt;/span&gt;         &lt;span style=&#34;color:#038&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;      Call to pairs:    &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;32&lt;/span&gt;        &lt;span style=&#34;color:#038&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Total count &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; pairs: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Total count &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; ipairs: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So it looked like the results table simply contained two entries, with keys of 9 and 32 (which correspond to where those emails happened to appear in my inbox). Calling ipairs yielded zero matches, which makes sense: there is no key of 1 (which is what Lua tables start with by convention, rather than 0 like almost everything else in the computer world :). The ipairs function goes through each key in order starting with 1 until a nil (undefined) key is found. In this case, 1 itself is nil. The output looks much different when I ran it using the new version (2.3) of imapfilter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[~/code] imapfilter -c ~/.imapfilter/test.lua
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  1      Call to pairs:    1              table: 0x82b0bd8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  2      Call to pairs:    2              table: 0x82b0c48
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  3      Call to pairs:    _union         function: 0x81d48d0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  4      Call to pairs:    _mt            table: 0x82a32d0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  5      Call to pairs:    mark_answered  function: 0x81cefe0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  6      Call to pairs:    send_query     function: 0x81d8180
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  7      Call to pairs:    is_flagged     function: 0x81c1878
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  8      Call to pairs:    unmark_deleted function: 0x81bd890
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  9      Call to pairs:    match_message  function: 0x81cd7f8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Total count for pairs: 68
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  1      Call to ipairs:    1        table: 0x82b0bd8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  2      Call to ipairs:    2        table: 0x82b0c48
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Total count for ipairs: 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This tells us a quite a few things, and solves the mystery of the 66, which represents some meta-data stored in the results table. So rather than treating results as a simple key/value hash with one entry per match, the results table is now a dual-purpose table where the hash part of it contains some meta-data, while the actual matches are stored in the array (indexed) part of the table. Note how the counting of the matches now starts at 1 and increments, rather than using the position in the inbox, as it did before. Which means we must use ipairs to iterate through the table and get our matching entries, in this case with keys 1 and 2.&lt;/p&gt;
&lt;p&gt;(If the “table” structure in Lua looks odd to you, that’s because it is. I don’t think I would have designed things that way myself—​while it’s clever to have a single structure that behaves as both an array with indices and a btree hash, it can lead to confusion and some ugly corner cases).&lt;/p&gt;
&lt;p&gt;The next step was to get my filters working again—​this was simply a matter of a global search and replace (M-x query-replace-regexp) from “pairs” to “ipairs”.This is a good a point as any to explain what my file looks like (stored as ~/.imapfilter/config.lua). The first part simply sets some common options—​for details on what they do, check out the &lt;a href=&#34;http://manpages.ubuntu.com/manpages/bionic/en/man5/imapfilter_config.5.html&#34;&gt;manpage for imapfilter_config&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.cache        = true
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.certificates = true
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.create       = false
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.info         = false
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.close        = true
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;options.expunge      = false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, a new table is created with the IMAP function. After that, we exclude all messages that are already marked as deleted, that have not yet been read, and have not been flagged. In other words, everything in my inbox I’ve already seen, but not flagged as urgent or deleted. The ‘*’ in this case is a logical ‘AND’, and the output is the search result table we saw in the above code.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;myaccount = IMAP {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  server   = &amp;#39;mail.example.com&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  username = &amp;#39;greg&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  password = &amp;#39;secret,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ssl      = &amp;#39;tls1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;baseresult = inbox:is_seen() * inbox:is_unflagged() * inbox:is_undeleted()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now that we have a search result, we simply start looking for things of interest and handling them. For example, to move messages to an existing IMAP folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-- Put Mediawiki messages into their folder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;result = baseresult
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  * (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    inbox:contain_to(&amp;#39;@lists.wikimedia.org&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    + inbox:contain_to(&amp;#39;@lists.wikimedia.org&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;count = 0 for k,v in ipairs(result) do count = count + 1 end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;if count &amp;gt; 0 then
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  inbox:move_messages(myaccount[&amp;#39;INBOX/mediawiki&amp;#39;], result)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(&amp;#39;Mediawiki emails moved:        &amp;#39; .. count)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Searches can be applied to an existing search result to create a new table. In the code above, a new table named ‘result’ is created that is based off of our ‘baseresult’ table, with the condition that only entries matching a specific “To” or “Cc” field are added.The ‘+’ acts as as a logical ‘OR’.&lt;/p&gt;
&lt;p&gt;Deletion is handled in a similar way:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-- Delete wiki alerts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;result = baseresult
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  * inbox:contain_from(&amp;#39;WikiAdmin &amp;lt;wikiadmin@example.com&amp;gt;&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  * inbox:contain_subject(&amp;#39;has been&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;count = 0 for k,v in ipairs(result) do count = count + 1 end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;if count &amp;gt; 0 then
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  inbox:delete_messages(result)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(&amp;#39;Wiki alerts deleted:           &amp;#39; .. count)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/wikiadmin@example.com&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The rest of my config.lua file is more filtering sections, similar to the above. Adding a new filter is as easy as creating a new section similar to the above by editing the ~/.imapfilter/config.lua file. While that’s not as automated as it could be, filter adjustment happens so rarely I have never been bothered by that step.&lt;/p&gt;
&lt;p&gt;If you are not using imapfilter, you should check it out, even if you are not using mutt; imapfilter is completely independent of your email reading program, and can be run from anywhere, as it doesn’t save or read anything locally. I find that imapfilter is very fast, so even when I used mail programs with built-in filters, I still employed imapfilter from time to time for bulk deletes and moves. Plus, it’s a great way to dip your toe into Lua if you are not familiar with it (although without using some of its more interesting features, such as coroutines).&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Review of The Book of IMAP</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2008/08/review-of-book-of-imap/"/>
      <id>https://www.endpointdev.com/blog/2008/08/review-of-book-of-imap/</id>
      <published>2008-08-26T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Former End Point employee Ryan Masters had his book review of the No Starch Press &lt;a href=&#34;http://www.osnews.com/story/20212/Book_Review_The_Book_of_IMAP&#34;&gt;published over at OSNews.com&lt;/a&gt;. Sounds like it was a decent book!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Switching from Sendmail to Postfix on OpenBSD</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2008/08/switching-from-sendmail-to-postfix-on/"/>
      <id>https://www.endpointdev.com/blog/2008/08/switching-from-sendmail-to-postfix-on/</id>
      <published>2008-08-01T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;It’s easy to pick on Sendmail, and with good reason. A poor security record, baroque configuration, slowness, painful configuration, monolithic design, and arcane configuration. Once you know Sendmail it’s bearable, and long-time experts aren’t always eager to give it up, but I wouldn’t recommend anyone deploy it for a serious mail server these days. But for a send-only mail daemon or a private, internal mail server, it works fine. Since it’s the default mailer for OpenBSD, and I haven’t been using OpenBSD as a heavy-traffic mail server, I’ve usually just left Sendmail in place.&lt;/p&gt;
&lt;p&gt;A few years ago some of our clients’ internal mail servers running Sendmail were getting heavy amounts of automated output from cron jobs, batch job output, transaction notifications, etc., and they bogged down and sometimes even stopped working entirely under the load. It wasn’t &lt;em&gt;that&lt;/em&gt; much email, though—​the machines should’ve been able to handle it.&lt;/p&gt;
&lt;p&gt;After trying to tune Sendmail to be more tolerant of heavy load and having little success, I finally switched to Postfix (which we had long used elsewhere) and the CPU load immediately dropped from 30+ down to below 1, and mail delivery worked without interruption during busy times.&lt;/p&gt;
&lt;p&gt;If I’d known how easy it is to switch OpenBSD from Sendmail to Postfix, I would’ve done it long ago. I wrongly figured it’d be hard since Sendmail is part of the base system, and none of that seemed very pluggable without hacking on things. I found out it was easy only by finally just trying it myself, following the very simple instructions, and having no trouble. I did this first on OpenBSD 3.9 and now again on OpenBSD 4.3, and the process was the same.&lt;/p&gt;
&lt;p&gt;First, pick an &lt;a href=&#34;https://www.openbsd.org/ftp.html&#34;&gt;OpenBSD mirror&lt;/a&gt;, and navigate to the appropriate packages directory. Then set up your environment for easy pkg_add usage. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export PKG_PATH=ftp://ftp.openbsd.org/pub/OpenBSD/4.3/packages/i386&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are several varying OpenBSD Postfix packages, offering support for lookups in LDAP, MySQL, Postgres, or SASL, or a simple build without any of those dependencies:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# pkg_add postfix
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ambiguous: postfix could be postfix-2.5.1p0 postfix-2.5.1p0-ldap postfix-2.5.1p0-mysql postfix-2.5.1p0-pgsql postfix-2.5.1p0-sasl2 postfix-2.6.20080216p1 postfix-2.6.20080216p1-ldap postfix-2.6.20080216p1-mysql postfix-2.6.20080216p1-pgsql postfix-2.6.20080216p1-sasl2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We’ll use the simple build:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pkg_add postfix-2.6.20080216p1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The output from the package installation tells you most of what you need to know, but I’ll break it down here with a little more detail.&lt;/p&gt;
&lt;p&gt;Run crontab -e as root and comment out this Sendmail job:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# sendmail clientmqueue runner
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#*/30    *       *       *       *       /usr/sbin/sendmail -L sm-msp-queue -Ac -q&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The sendmail compatibility is implemented by a wrapper script similar to how Debian’s alternatives system does it (and which Red Hat borrowed as well). In OpenBSD, the wrapper is a binary that uses the configuration in /etc/mailer.conf to decide what to actually run, as opposed to using symlinks as the alternatives system does. You can see this here:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# ls -lFa /usr/sbin/sendmail /usr/bin/newaliases /usr/bin/mailq
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lrwxr-xr-x  1 root  wheel  21 Aug  1 14:50 /usr/bin/mailq@ -&amp;gt; /usr/sbin/mailwrapper
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lrwxr-xr-x  1 root  wheel  21 Aug  1 14:50 /usr/bin/newaliases@ -&amp;gt; /usr/sbin/mailwrapper
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lrwxr-xr-x  1 root  wheel  21 Aug  1 14:51 /usr/sbin/sendmail@ -&amp;gt; /usr/sbin/mailwrapper&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To make the switch to Postfix, run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/usr/local/sbin/postfix-enable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you’re ready to configure /etc/postfix/main.cf as needed. The defaults should be fine for a server sending outgoing mail only, though if you followed the OpenBSD installer’s instructions to use only the short name for the hostname, you need to either set the mydomain parameter manually in main.cf, or else edit /etc/myname to use a fully-qualified domain name instead of the hostname only (and update immediately with the hostname command as well). I do the latter and haven’t had any trouble with it before.&lt;/p&gt;
&lt;p&gt;Stop Sendmail and start Postfix the same way the boot script will do it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pkill sendmail
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/usr/sbin/sendmail -bd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Send a test message and make sure you receive it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &amp;#34;A special test message&amp;#34; | mail -s testing &amp;lt;em&amp;gt;your_account@the.domain&amp;lt;/em&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that if you send your message to somewhere offsite, spam filters may reject it if your sending server doesn’t have a real hostname, a reverse DNS pointer for the IP address, etc. You can just send locally to avoid that, but of course you won’t be able to send mail offsite until you deal with those problems.&lt;/p&gt;
&lt;p&gt;Add these settings to /etc/rc.conf.local so Postfix will start on boot:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sendmail_flags=&amp;#34;-bd&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;syslogd_flags=&amp;#34;-a /var/spool/postfix/dev/log&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now reboot to make sure everything comes up correctly on its own and to get syslogd going right. Send yourself another test message, and you can move on!&lt;/p&gt;
&lt;p&gt;Many thanks to the &lt;a href=&#34;http://www.postfix.org/&#34;&gt;Postfix&lt;/a&gt; developers for the excellent mail server software and to the OpenBSD developers for a nice easy way to switch the system mail daemon.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Death, Taxes — and Spam‽</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2005/06/death-taxes-and-spam/"/>
      <id>https://www.endpointdev.com/blog/2005/06/death-taxes-and-spam/</id>
      <published>2005-06-12T00:00:00+00:00</published>
      <author>
        <name>Brian Dunn</name>
      </author>
      <content type="html">
        &lt;p&gt;End Point has been hard at work keeping spam from reaching our clients’ email boxes.&lt;/p&gt;
&lt;p&gt;In December 2004, End Point installed new email filters, powered by two state-of-the-art packages: amavisd-new and SpamAssassin.&lt;/p&gt;
&lt;p&gt;By filtering out the most egregious junk emails at the server level, far fewer undesirable emails reach our clients’ inboxes. Real mail downloads to their inboxes faster, and the painful morning routine of going through the pile of junk mail delivered overnight is gone. That time savings multiplied for each employee really adds up.&lt;/p&gt;
&lt;p&gt;We’re gratified to hear about the positive, immediate difference this change has made for our current email services clients. If you’re not currently using End Point’s email service, please &lt;a href=&#34;/contact/&#34;&gt;contact us&lt;/a&gt;. We’d be happy to discuss your requirements and explain how we can help meet your email needs.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
