<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sagan Marketing, LLC</title>
    <description>The latest articles on DEV Community by Sagan Marketing, LLC (@saganmarketing).</description>
    <link>https://dev.to/saganmarketing</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3597039%2F3ec5e760-80b5-48f5-9b7e-659dda670e71.png</url>
      <title>DEV Community: Sagan Marketing, LLC</title>
      <link>https://dev.to/saganmarketing</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saganmarketing"/>
    <language>en</language>
    <item>
      <title>Outlook.com Is the Final Boss of 'Just Send an Email'</title>
      <dc:creator>Sagan Marketing, LLC</dc:creator>
      <pubDate>Mon, 25 May 2026 05:21:20 +0000</pubDate>
      <link>https://dev.to/saganmarketing/outlookcom-is-the-final-boss-of-just-send-an-email-2gi4</link>
      <guid>https://dev.to/saganmarketing/outlookcom-is-the-final-boss-of-just-send-an-email-2gi4</guid>
      <description>&lt;p&gt;Every email project starts with a lie:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'll just test it with SMTP real quick."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was my plan while working on &lt;strong&gt;&lt;a href="http://www.epicmail.org" rel="noopener noreferrer"&gt;EpicMail&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I wanted a boring, repeatable provider smoke test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a mailbox provider.&lt;/li&gt;
&lt;li&gt;Add host, port, username, and password.&lt;/li&gt;
&lt;li&gt;Send one message.&lt;/li&gt;
&lt;li&gt;Move on with my life.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead, I got a tour of how consumer email providers have slowly turned "send an email" into an authentication archaeology dig.&lt;/p&gt;

&lt;p&gt;The three providers I tested were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gmail&lt;/li&gt;
&lt;li&gt;iCloud Mail&lt;/li&gt;
&lt;li&gt;Outlook.com&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The surprise was not that OAuth is complicated. Everyone knows OAuth is complicated.&lt;/p&gt;

&lt;p&gt;The surprise was how much easier the entire C# provider-testing loop became once I stopped trying to solve OAuth on day one and used &lt;strong&gt;app passwords&lt;/strong&gt; for developer-owned test accounts.&lt;/p&gt;

&lt;p&gt;This is not an argument that app passwords are better than OAuth.&lt;/p&gt;

&lt;p&gt;It is an argument that they are a fantastic debugging ladder.&lt;/p&gt;

&lt;p&gt;But maybe shipping the ladder as the building is not the answer also.&lt;/p&gt;

&lt;h2&gt;
  
  
  First C# lesson: do not start with &lt;code&gt;System.Net.Mail.SmtpClient&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you are building this in .NET, the first trap is that the built-in class name looks exactly like what you want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Mail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SmtpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"smtp.example.com"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Microsoft's own docs say &lt;code&gt;System.Net.Mail.SmtpClient&lt;/code&gt; is not recommended for new development because it does not support many modern protocols, and they point developers toward MailKit or other libraries instead.&lt;/p&gt;

&lt;p&gt;So for EpicMail-style testing, I reached for MailKit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package MailKit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The boring version of the test looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;MailKit.Net.Smtp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;MailKit.Security&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;MimeKit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;SmtpSmokeTestOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SendSmokeTestAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SmtpSmokeTestOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MimeMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MailboxAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MailboxAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"EpicMail smoke test: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TextPart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"If this arrived, SMTP is alive."&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SmtpClient&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;15_000&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConnectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;SecureSocketOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartTls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthenticateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisconnectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the code I wanted the entire project to feel like.&lt;/p&gt;

&lt;p&gt;A tiny abstraction.&lt;/p&gt;

&lt;p&gt;A tiny config object.&lt;/p&gt;

&lt;p&gt;A tiny smoke test.&lt;/p&gt;

&lt;p&gt;And then reality showed up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The provider matrix I wanted
&lt;/h2&gt;

&lt;p&gt;For a developer-owned test account, the provider table feels like it &lt;em&gt;should&lt;/em&gt; be this simple:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;SMTP host&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Credential path for smoke testing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gmail&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smtp.gmail.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;587&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google app password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iCloud Mail&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smtp.mail.me.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;587&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Apple app-specific password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outlook.com&lt;/td&gt;
&lt;td&gt;&lt;code&gt;smtp-mail.outlook.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;587&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Microsoft app password for the controlled test path; OAuth2/Modern Auth for the product path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As configuration, that becomes extremely boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"SmtpSmokeTests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gmail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smtp.gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"you@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;gmail app password&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iCloud"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smtp.mail.me.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"you@icloud.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;apple app-specific password&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Outlook.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smtp-mail.outlook.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"you@outlook.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;microsoft app password or oauth token path&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The protocol part is not the hard part.&lt;/p&gt;

&lt;p&gt;The hard part is proving to three giant identity systems that your tiny test harness is not suspicious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gmail: the trick is not your normal password
&lt;/h2&gt;

&lt;p&gt;Gmail's SMTP settings are conventional enough: &lt;code&gt;smtp.gmail.com&lt;/code&gt;, port &lt;code&gt;587&lt;/code&gt;, STARTTLS, authentication required.&lt;/p&gt;

&lt;p&gt;The trick is that the useful test credential is not your normal Google password.&lt;/p&gt;

&lt;p&gt;It is a generated app password.&lt;/p&gt;

&lt;p&gt;That turns this kind of failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;535-5.7.8 Username and Password not accepted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into this kind of progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;250 2.1.5 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a smoke test, that is a massive difference.&lt;/p&gt;

&lt;p&gt;No consent screen.&lt;/p&gt;

&lt;p&gt;No redirect URI.&lt;/p&gt;

&lt;p&gt;No token refresh.&lt;/p&gt;

&lt;p&gt;No "is my OAuth app still in testing mode?" rabbit hole.&lt;/p&gt;

&lt;p&gt;Just a dev mailbox and a generated credential.&lt;/p&gt;

&lt;p&gt;Google's own Gmail client setup docs list &lt;code&gt;smtp.gmail.com&lt;/code&gt;, STARTTLS on port &lt;code&gt;587&lt;/code&gt;, and suggest trying an app password when 2-Step Verification is enabled and a mail client cannot sign in.&lt;/p&gt;

&lt;h2&gt;
  
  
  iCloud Mail: refreshingly literal
&lt;/h2&gt;

&lt;p&gt;iCloud Mail was the provider that felt closest to the old-school SMTP mental model.&lt;/p&gt;

&lt;p&gt;Apple's settings are direct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMTP server: smtp.mail.me.com
Port: 587
Authentication: required
Username: full iCloud email address
Password: app-specific password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is the username.&lt;/p&gt;

&lt;p&gt;For SMTP, Apple says to use the full iCloud Mail email address, not just the local part.&lt;/p&gt;

&lt;p&gt;So this is good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you@icloud.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the kind of tiny detail that costs 30 minutes when your test harness only says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication failed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it is exactly why provider diagnostics matter.&lt;/p&gt;

&lt;p&gt;A good product should say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iCloud rejected SMTP authentication.

Check:
- Are you using smtp.mail.me.com on port 587?
- Are you using STARTTLS?
- Is the username your full iCloud Mail address?
- Is the password an Apple app-specific password rather than your Apple Account password?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not just error handling.&lt;/p&gt;

&lt;p&gt;That is product UX.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook.com: where SMTP becomes an identity-platform problem
&lt;/h2&gt;

&lt;p&gt;Outlook.com is where the simple smoke test started to feel like a boss fight.&lt;/p&gt;

&lt;p&gt;The rough shape still looks familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMTP server: smtp-mail.outlook.com
Port: 587
Encryption: STARTTLS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then the auth story gets more interesting. With a new Outlook.com test account, I was not able to turn on SMTP... it looked like turned it on, but when I refreshed the page, it showed that it was still off. When I tried to investigate deeper, I noticed that the "Inspect/Developer Tools" Network tab showed an error in the response:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;POST &lt;a href="https://outlook.live.com/owa/0/service.svc?action=SetConsumerMailbox&amp;amp;app=Mail&amp;amp;n=36" rel="noopener noreferrer"&gt;https://outlook.live.com/owa/0/service.svc?action=SetConsumerMailbox&amp;amp;app=Mail&amp;amp;n=36&lt;/a&gt;&lt;br&gt;
412 (Precondition Failed)&lt;br&gt;
Uncaught (in promise) Error: SetConsumerMailbox failed:&lt;br&gt;
Microsoft.Exchange.Clients.Owa2.Server.Core.OwaInvalidServiceRequestException&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Furthermore, Microsoft's Outlook.com SMTP settings list OAuth2 / Modern Auth as the authentication method. The same support surface also documents app passwords for Outlook.com accounts using two-factor authentication when a password is not accepted by another program.&lt;/p&gt;

&lt;p&gt;That is where the provider test stops being a socket problem and becomes an account-state problem.&lt;/p&gt;

&lt;p&gt;When Outlook.com rejects you, the question is not just:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did my C# code authenticate correctly?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which authentication universe is this account currently living in?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a developer-owned EpicMail smoke test, an app password was the shortest path to answering the narrow question I cared about first:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this adapter send one message through Outlook.com SMTP?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But I would not treat that as the long-term product answer.&lt;/p&gt;

&lt;p&gt;For real user onboarding, Outlook.com is clearly pointing the ecosystem toward Modern Auth / OAuth2.&lt;/p&gt;

&lt;p&gt;That is the central distinction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dev-owned smoke test:
  App password is great.

User-owned production account:
  OAuth2 / provider API is the responsible path.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that both of those workflows might touch the same SMTP host and port.&lt;/p&gt;

&lt;p&gt;So your code has to know which game it is playing.&lt;/p&gt;

&lt;h2&gt;
  
  
  App passwords are not "better OAuth"
&lt;/h2&gt;

&lt;p&gt;This is the key lesson.&lt;/p&gt;

&lt;p&gt;App passwords are not a better security model than OAuth.&lt;/p&gt;

&lt;p&gt;They are a faster way to answer a narrower engineering question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this provider send mail from this controlled test account using SMTP?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For EpicMail, that was exactly the question I needed answered first.&lt;/p&gt;

&lt;p&gt;The app-password credential shape is tiny:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;AppPasswordCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AppPassword&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The send path is tiny:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthenticateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OAuth is a different animal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OAuthSmtpCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;RefreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt; &lt;span class="n"&gt;ExpiresAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ProviderClientId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And yes, MailKit can authenticate with an OAuth2 access token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;MailKit.Security&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;oauth2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SaslMechanismOAuth2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthenticateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauth2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that line is not the hard part.&lt;/p&gt;

&lt;p&gt;The hard part is everything required to make that line safe and repeatable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provider app registration&lt;/li&gt;
&lt;li&gt;redirect URIs&lt;/li&gt;
&lt;li&gt;local development callback URLs&lt;/li&gt;
&lt;li&gt;consent screen configuration&lt;/li&gt;
&lt;li&gt;scopes&lt;/li&gt;
&lt;li&gt;token exchange&lt;/li&gt;
&lt;li&gt;refresh token storage&lt;/li&gt;
&lt;li&gt;encryption at rest&lt;/li&gt;
&lt;li&gt;token refresh&lt;/li&gt;
&lt;li&gt;revoked consent&lt;/li&gt;
&lt;li&gt;expired refresh tokens&lt;/li&gt;
&lt;li&gt;tenant or account policy&lt;/li&gt;
&lt;li&gt;provider-specific support docs&lt;/li&gt;
&lt;li&gt;user-facing recovery flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a real multi-user product, that work is justified.&lt;/p&gt;

&lt;p&gt;For a smoke test, it can be a trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The abstraction changed once I separated the two paths
&lt;/h2&gt;

&lt;p&gt;The useful design move was to stop pretending that "SMTP auth" is one thing.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;At minimum, EpicMail needs to model two credential modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ProviderCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;AppPasswordCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AppPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ProviderCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OAuthCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;RefreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt; &lt;span class="n"&gt;ExpiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ProviderCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the adapter can be honest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IEmailProviderAdapter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ProviderName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SendSmokeTestAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ProviderCredential&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;ProviderDiagnostic&lt;/span&gt; &lt;span class="nf"&gt;Diagnose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Outlook.com adapter can say something useful instead of pretending all failures are equal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProviderName&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Outlook.com"&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;AppPasswordCredential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;diagnostic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"App passwords are useful for controlled Outlook.com smoke tests, "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"but OAuth2 / Modern Auth is the better user-facing path."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That warning is not bureaucracy.&lt;/p&gt;

&lt;p&gt;It is future you leaving a note for support-ticket you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error taxonomy became the real feature
&lt;/h2&gt;

&lt;p&gt;The most useful part of this work was not the successful send.&lt;/p&gt;

&lt;p&gt;It was cataloging failures.&lt;/p&gt;

&lt;p&gt;The first version of an email integration usually thinks it needs this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;Sent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it actually needs is closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ProviderDiagnosticCode&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TcpConnectionFailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;StartTlsFailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AuthenticationRejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SenderRejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RecipientRejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ProviderPolicyRejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RateLimited&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MessageRejected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Unknown&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because users do not need to know that &lt;code&gt;SmtpCommandException&lt;/code&gt; happened.&lt;/p&gt;

&lt;p&gt;They need to know what to do next.&lt;/p&gt;

&lt;p&gt;A raw error says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MailKit.Security.AuthenticationException: Authentication failed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful EpicMail-style diagnostic says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outlook.com rejected SMTP authentication.

Check:
- Are you using smtp-mail.outlook.com on port 587?
- Are you using STARTTLS?
- Is this account expecting OAuth2 / Modern Auth?
- If this is a dev-owned smoke test, is the Microsoft app password valid?
- Did Microsoft flag the sign-in in Recent Activity?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the difference between supporting SMTP and understanding email providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The C# testing loop I wish I had started with
&lt;/h2&gt;

&lt;p&gt;Instead of starting with "send an email," I wish I had started with a checklist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"Can resolve SMTP host"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can open TCP connection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can upgrade with STARTTLS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can authenticate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can send MAIL FROM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can send RCPT TO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can send DATA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can receive final provider response"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Can map provider error to setup advice"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The happy path is one row in that table.&lt;/p&gt;

&lt;p&gt;The product is the rest of the table.&lt;/p&gt;

&lt;p&gt;For each provider, I now want cases like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ valid app password
❌ normal account password
❌ revoked app password
❌ wrong SMTP host
❌ wrong port
❌ STARTTLS disabled
❌ username missing full iCloud address
❌ Outlook.com account expects OAuth2 / Modern Auth
❌ provider flags suspicious login
❌ provider accepts login but rejects sender
❌ provider rate-limits or policy-blocks the message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is where the value is.&lt;/p&gt;

&lt;p&gt;A generic SMTP client can throw raw provider errors.&lt;/p&gt;

&lt;p&gt;A product should translate them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical rule I ended up with
&lt;/h2&gt;

&lt;p&gt;My current rule for EpicMail is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For developer-owned smoke tests:
  Use app passwords when the provider supports them.

For user-owned accounts:
  Use OAuth2 / provider-approved auth flows.

For serious production sending:
  Consider transactional email providers instead of consumer inboxes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;App passwords made Gmail, iCloud Mail, and Outlook.com easier to compare because they reduced the test surface area.&lt;/p&gt;

&lt;p&gt;That mattered.&lt;/p&gt;

&lt;p&gt;It let me test the boring-but-critical pieces first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SMTP host and port&lt;/li&gt;
&lt;li&gt;STARTTLS behavior&lt;/li&gt;
&lt;li&gt;message construction&lt;/li&gt;
&lt;li&gt;sender validation&lt;/li&gt;
&lt;li&gt;recipient validation&lt;/li&gt;
&lt;li&gt;provider-specific rejection messages&lt;/li&gt;
&lt;li&gt;useful diagnostics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then OAuth can come later as a product-grade authentication layer instead of blocking the first transport-layer proof.&lt;/p&gt;

&lt;p&gt;OAuth is the production path.&lt;/p&gt;

&lt;p&gt;App passwords are the debugging ladder.&lt;/p&gt;

&lt;p&gt;Do not ship the ladder as the building... or if you do then version 1.1 should be adding OATH to Outlook, Gmail, iCloud... probably in that order too.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.google.com/mail/answer/7104828?hl=en" rel="noopener noreferrer"&gt;Gmail Help: Gmail SMTP settings and app password troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.apple.com/en-us/102525" rel="noopener noreferrer"&gt;Apple Support: iCloud Mail server settings for other email client apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.microsoft.com/en-us/office/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040" rel="noopener noreferrer"&gt;Microsoft Support: POP, IMAP, and SMTP settings for Outlook.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.microsoft.com/en-gb/office/add-your-outlook-com-account-in-outlook-for-windows-642c1902-bdd9-4dc3-abe7-76d60b148b23" rel="noopener noreferrer"&gt;Microsoft Support: Generate an app password for Outlook.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.mail.smtpclient" rel="noopener noreferrer"&gt;Microsoft Learn: &lt;code&gt;System.Net.Mail.SmtpClient&lt;/code&gt; is not recommended for new development&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mail</category>
      <category>smtp</category>
      <category>oauth</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Free Website Uptime API — JSON for Developers and AI Integrations</title>
      <dc:creator>Sagan Marketing, LLC</dc:creator>
      <pubDate>Wed, 05 Nov 2025 11:07:01 +0000</pubDate>
      <link>https://dev.to/saganmarketing/free-website-uptime-api-json-for-developers-and-ai-integrations-519m</link>
      <guid>https://dev.to/saganmarketing/free-website-uptime-api-json-for-developers-and-ai-integrations-519m</guid>
      <description>&lt;h1&gt;
  
  
  🚀 Launch Announcement
&lt;/h1&gt;

&lt;p&gt;We’re excited to share that &lt;a href="https://siteinformant.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Site Informant&lt;/strong&gt;&lt;/a&gt; — the affordable website uptime monitoring service — now provides a &lt;strong&gt;public JSON API&lt;/strong&gt; for developers and AI tools.&lt;/p&gt;

&lt;p&gt;With one simple GET request, you can pull live uptime, response-time, and SSL-certificate data for any site you monitor (or any site that’s opted-in for public visibility).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET https://api.siteinformant.com/api/public/status/prudentdev.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Example response&lt;br&gt;
{&lt;br&gt;
 "domain": "prudentdev.com",&lt;br&gt;
 "uptimePercent": 100,&lt;br&gt;
 "isOnline": true,&lt;br&gt;
 "lastCheckUtc": "2025-11-04T07:45:00Z",&lt;br&gt;
 "averageResponseMs": 238,&lt;br&gt;
 "sslExpiresUtc": "2026-01-04T00:00:00Z",&lt;br&gt;
 "sslIssuer": "Let's Encrypt"&lt;br&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🧠 &lt;strong&gt;Why it matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No API key required&lt;/p&gt;

&lt;p&gt;Perfect for dashboards, chatbots, and AI monitoring agents&lt;/p&gt;

&lt;p&gt;Data available for any public site you manage through Site Informant&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.siteinformant.com/api/public/openapi.json" rel="noopener noreferrer"&gt;OpenAPI spec&lt;/a&gt; for instant integration&lt;/p&gt;

&lt;p&gt;🛠️ &lt;strong&gt;For developers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out our full &lt;a href="https://siteinformant.com/Developers" rel="noopener noreferrer"&gt;Developers&lt;/a&gt; page&lt;br&gt;
 with C# and curl examples, rate-limit info, and integration notes.&lt;/p&gt;

&lt;p&gt;🌐 &lt;strong&gt;Try it free&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monitor one site free, and add more for only $1/month per 5 sites.&lt;br&gt;
Start here →  &lt;a href="https://siteinformant.com" rel="noopener noreferrer"&gt;https://siteinformant.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://siteinformant.com/blog/free-website-uptime-api" rel="noopener noreferrer"&gt;siteinformant.com/blog/free-website-uptime-api&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>sitereliabilityengineering</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
