<?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: Damiana Teliti</title>
    <description>The latest articles on DEV Community by Damiana Teliti (@damiana_teliti_5b0178f9f4).</description>
    <link>https://dev.to/damiana_teliti_5b0178f9f4</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4200978%2F3c93cde1-666d-424c-bc59-68c4c47e07a5.png</url>
      <title>DEV Community: Damiana Teliti</title>
      <link>https://dev.to/damiana_teliti_5b0178f9f4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/damiana_teliti_5b0178f9f4"/>
    <language>en</language>
    <item>
      <title>Give your AI assistant a real email verifier (MCP setup in 2 minutes)</title>
      <dc:creator>Damiana Teliti</dc:creator>
      <pubDate>Fri, 23 Oct 2026 12:48:09 +0000</pubDate>
      <link>https://dev.to/damiana_teliti_5b0178f9f4/give-your-ai-assistant-a-real-email-verifier-mcp-setup-in-2-minutes-2g28</link>
      <guid>https://dev.to/damiana_teliti_5b0178f9f4/give-your-ai-assistant-a-real-email-verifier-mcp-setup-in-2-minutes-2g28</guid>
      <description>&lt;p&gt;Ask an AI assistant whether an email address is real and it will answer confidently. But without a tool it can't look up the domain's mail servers or ask the mailbox whether it exists. It can only judge whether the address &lt;em&gt;looks&lt;/em&gt; plausible, which is exactly how typos and dead mailboxes slip through.&lt;/p&gt;

&lt;p&gt;The fix is to give the assistant a real tool. With the Model Context Protocol (MCP), Claude, Cursor and other clients can call an email verifier directly, and you get answers backed by DNS and SMTP instead of pattern matching.&lt;/p&gt;

&lt;p&gt;Here's how to set that up in a couple of minutes, and the one design rule I think every paid MCP tool should follow. The examples use ListCheckup's MCP server (disclosure: I work on it), but the pattern applies to any verifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the assistant gets
&lt;/h2&gt;

&lt;p&gt;Six tools:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tool&lt;/th&gt;
&lt;th&gt;what it does&lt;/th&gt;
&lt;th&gt;costs credits?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check_email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;verify one address&lt;/td&gt;
&lt;td&gt;yes, unless catch-all or unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_balance&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;remaining credits&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create_list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;submit a list, get a price back&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;start_list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;start checking the list&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes, the only one&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;status and progress&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_list_summary&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;counts when finished&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Setup in Claude Code
&lt;/h2&gt;

&lt;p&gt;One command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http listcheckup https://listcheckup.com/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer lck_live_..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup in Claude Desktop, Cursor and most other clients
&lt;/h2&gt;

&lt;p&gt;Add this to the client's MCP config file:&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;"mcpServers"&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;"listcheckup"&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;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://listcheckup.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&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;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer lck_live_..."&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;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;To test it, ask the assistant to list its tools. Six tools means you're connected. An &lt;code&gt;unauthorized&lt;/code&gt; error almost always means the word &lt;code&gt;Bearer&lt;/code&gt; (and the space after it) is missing, or the key was pasted incompletely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can ask
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"Is &lt;a href="mailto:jane@acme.com"&gt;jane@acme.com&lt;/a&gt; a real address?"&lt;/li&gt;
&lt;li&gt;"How many credits do I have left?"&lt;/li&gt;
&lt;li&gt;"Here's a CSV of 4,000 leads. How much would it cost to verify them?"&lt;/li&gt;
&lt;li&gt;"Go ahead and start it, then tell me how many came back invalid."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The design rule: price first, spend second
&lt;/h2&gt;

&lt;p&gt;Notice that &lt;code&gt;create_list&lt;/code&gt; and &lt;code&gt;start_list&lt;/code&gt; are separate tools. That's deliberate. An agent that can spend money in one step will eventually spend it when you only asked a question. Splitting "what would this cost?" from "do it" means the assistant has to show you the price, and you have a natural point to say yes or no.&lt;/p&gt;

&lt;p&gt;If you're building an MCP server for anything that costs money, I'd copy this. Read-only and quoting tools should be free and safe to call; exactly one tool should spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the key safe
&lt;/h2&gt;

&lt;p&gt;The key sits in clear text in the config file, and anyone holding it can spend your credits. Keep that file out of shared repos, use one key per machine so you can revoke a single one, and revoke from the dashboard if a key leaks. Revocation applies to the API and the MCP server immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why catch-all and unknown are free
&lt;/h2&gt;

&lt;p&gt;Some domains accept every address (catch-all), and some servers refuse to answer verification probes (unknown). No verifier can confirm those, so ListCheckup doesn't charge for them. There are also 100 free checks every month, which is plenty to try this out. Docs: &lt;a href="https://listcheckup.com/api?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=mcp_tutorial" rel="noopener noreferrer"&gt;listcheckup.com/api&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Are you giving your assistants tools that spend money yet? How are you handling confirmation?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>mcp</category>
      <category>tools</category>
    </item>
    <item>
      <title>How to verify emails at signup without blocking real users (Node.js)</title>
      <dc:creator>Damiana Teliti</dc:creator>
      <pubDate>Fri, 23 Oct 2026 11:22:48 +0000</pubDate>
      <link>https://dev.to/damiana_teliti_5b0178f9f4/how-to-verify-emails-at-signup-without-blocking-real-users-nodejs-14nh</link>
      <guid>https://dev.to/damiana_teliti_5b0178f9f4/how-to-verify-emails-at-signup-without-blocking-real-users-nodejs-14nh</guid>
      <description>&lt;p&gt;Most signup forms validate an email with a regex and call it done. That catches &lt;code&gt;bob@@gmail&lt;/code&gt; but happily accepts &lt;code&gt;jane@compnay.com&lt;/code&gt;, &lt;code&gt;test@mailinator.com&lt;/code&gt; and &lt;code&gt;ceo@a-domain-that-accepts-everything.com&lt;/code&gt;. A month later your welcome emails are bouncing and your sender reputation is paying for it.&lt;/p&gt;

&lt;p&gt;Here's a small, practical pattern for checking addresses at signup without blocking real users. The code uses Node.js and the ListCheckup API (disclosure: I work on it), but the decision logic at the end applies to any verifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real check tells you
&lt;/h2&gt;

&lt;p&gt;A proper verifier goes beyond syntax: it looks up the domain's MX records and asks the recipient's mail server whether the mailbox exists, without sending anything. You get back one of five answers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;status&lt;/th&gt;
&lt;th&gt;meaning&lt;/th&gt;
&lt;th&gt;what to do at signup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;valid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;mailbox exists&lt;/td&gt;
&lt;td&gt;accept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invalid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;can't receive mail&lt;/td&gt;
&lt;td&gt;reject, ask them to fix it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;risky&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;disposable or shared inbox&lt;/td&gt;
&lt;td&gt;accept or warn, your call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;catch_all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;domain accepts every address&lt;/td&gt;
&lt;td&gt;accept, flag for later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unknown&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;server wouldn't answer&lt;/td&gt;
&lt;td&gt;accept, flag for later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important part is the last two. &lt;strong&gt;Never block a signup on &lt;code&gt;catch_all&lt;/code&gt; or &lt;code&gt;unknown&lt;/code&gt;.&lt;/strong&gt; Plenty of real companies run catch-all domains, and plenty of real mail servers refuse to answer verification probes. Rejecting those turns away paying customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The handler
&lt;/h2&gt;

&lt;p&gt;One request per signup, answered inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// verify-email.js (Node 18+, no dependencies)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://listcheckup.com/v1/verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkSignupEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// never hold up signup&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LISTCHECKUP_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verifier_unavailable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// { status, reason, confidence, ... }&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;risky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;catch_all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// timeout or network error: fail open, don't punish the user&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verifier_timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&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;Three design choices worth copying whatever tool you use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fail open.&lt;/strong&gt; If the verifier is slow or down, accept the signup. A verification service should never be a single point of failure for your funnel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard timeout.&lt;/strong&gt; Most checks answer in well under a second, but some mail servers are slow. Four seconds is a reasonable ceiling for a form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store the flag.&lt;/strong&gt; Save &lt;code&gt;catch_all&lt;/code&gt; / &lt;code&gt;unknown&lt;/code&gt; on the user record. You'll want it later when deciding who gets your first newsletter.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Using it in a route
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;checkSignupEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;That address can't receive email. Could there be a typo?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email_unconfirmed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;email_risky&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Note the error message: "can't receive email, could there be a typo?" is friendlier than "invalid email", because many invalid addresses at signup are typos, not bad actors.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about lists you already have?
&lt;/h2&gt;

&lt;p&gt;Checking one address at a time is for forms. For an existing list, send the whole thing in one request, get a price back before anything is charged, then start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://listcheckup.com/v1/lists"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$LISTCHECKUP_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "old-subscribers", "emails": ["a@example.com", "b@example.com"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A webhook fires when it finishes, so you don't need to poll.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pricing detail I'd check in any verifier
&lt;/h2&gt;

&lt;p&gt;Since no verifier can confirm &lt;code&gt;catch_all&lt;/code&gt; or &lt;code&gt;unknown&lt;/code&gt; addresses, I think you shouldn't pay for them. ListCheckup doesn't charge for those two results, and there are 100 free checks a month, which is plenty for testing a signup flow. The full API reference is at &lt;a href="https://listcheckup.com/api?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=signup_tutorial" rel="noopener noreferrer"&gt;listcheckup.com/api&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How do you handle email checks at signup today: regex only, double opt-in, or a verifier?&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>email</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why catch-all email addresses can't be verified (and what to do instead)</title>
      <dc:creator>Damiana Teliti</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:30:35 +0000</pubDate>
      <link>https://dev.to/damiana_teliti_5b0178f9f4/why-catch-all-email-addresses-cant-be-verified-and-what-to-do-instead-30j3</link>
      <guid>https://dev.to/damiana_teliti_5b0178f9f4/why-catch-all-email-addresses-cant-be-verified-and-what-to-do-instead-30j3</guid>
      <description>&lt;p&gt;If you've ever run a list through an email verifier, you've probably seen a result labelled &lt;strong&gt;catch-all&lt;/strong&gt;, &lt;strong&gt;accept-all&lt;/strong&gt; or &lt;strong&gt;unknown&lt;/strong&gt;. It's tempting to treat that as a bug in the tool. It isn't. It's a hard limit of how email works, and once you understand it you'll make better decisions about what to send to.&lt;/p&gt;

&lt;h2&gt;
  
  
  How verification works under the hood
&lt;/h2&gt;

&lt;p&gt;A verifier never sends an email. It runs a short conversation with the recipient's mail server and stops just before the message would be delivered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Syntax check.&lt;/strong&gt; Is &lt;code&gt;anna@example.com&lt;/code&gt; even a well-formed address?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS lookup.&lt;/strong&gt; Does &lt;code&gt;example.com&lt;/code&gt; publish MX records, i.e. does anything accept mail for this domain?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SMTP handshake.&lt;/strong&gt; Connect to the mail server and say who the mail is for:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;EHLO verifier.example
MAIL FROM:&amp;lt;probe@verifier.example&amp;gt;
RCPT TO:&amp;lt;anna@example.com&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is the reply to &lt;code&gt;RCPT TO&lt;/code&gt;:&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                       -&amp;gt; mailbox accepted
550 5.1.1 User unknown             -&amp;gt; mailbox does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the verifier sends &lt;code&gt;QUIT&lt;/code&gt;. No message is ever transmitted. For most domains, that &lt;code&gt;250&lt;/code&gt; versus &lt;code&gt;550&lt;/code&gt; is a clean answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it breaks: catch-all domains
&lt;/h2&gt;

&lt;p&gt;Some mail servers are configured to accept &lt;strong&gt;every&lt;/strong&gt; address at the domain. Ask about a real mailbox and you get &lt;code&gt;250 OK&lt;/code&gt;. Ask about &lt;code&gt;xk2j9q-not-a-person@example.com&lt;/code&gt; and you also get &lt;code&gt;250 OK&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Companies do this for legitimate reasons: so a typo in an address still reaches someone, so mail to ex-employees lands in a shared inbox, or because their security gateway accepts everything first and filters later.&lt;/p&gt;

&lt;p&gt;From the outside, a real mailbox and an invented one now look identical. The usual detection trick is to probe a random address that almost certainly doesn't exist. If that gets &lt;code&gt;250&lt;/code&gt;, the domain is catch-all. But that only tells you &lt;em&gt;the domain accepts everything&lt;/em&gt;. It tells you nothing about whether the one address you actually care about belongs to a person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No SMTP-based check can resolve this.&lt;/strong&gt; Any tool that confidently labels these addresses "valid" is guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other reasons you'll see "unknown"
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Greylisting.&lt;/strong&gt; The server temporarily rejects the first attempt (&lt;code&gt;451 4.7.1&lt;/code&gt;) and expects a retry minutes later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting and blocks.&lt;/strong&gt; Large providers throttle or refuse verification-style connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts.&lt;/strong&gt; The server simply doesn't answer in time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good verifier retries some of these, but some addresses will stay undetermined, and that's the honest result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with catch-all addresses
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep them as a separate segment.&lt;/strong&gt; Don't mix them into your "verified" list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send to the most engaged ones first&lt;/strong&gt; (people who opened or clicked recently) and watch your bounce rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cap the volume.&lt;/strong&gt; Hard bounces above roughly 2% of a send are where mailbox providers start to push back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use other signals.&lt;/strong&gt; A recent reply, a signed-up account or an address you got directly from the person is worth more than any verifier result.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The pricing question
&lt;/h2&gt;

&lt;p&gt;Here's the part I'd encourage you to check in whatever tool you use: &lt;strong&gt;do you get charged for results the tool can't actually determine?&lt;/strong&gt; Many verifiers bill a catch-all or unknown result exactly like a confirmed one, and some count catch-alls as valid in their accuracy claims.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure:&lt;/em&gt; I work on &lt;a href="https://listcheckup.com" rel="noopener noreferrer"&gt;ListCheckup&lt;/a&gt;, an EU-based email verifier. We label catch-all and unknown addresses separately and refund the credit for them, precisely because we can't honestly confirm them. There's a longer write-up on &lt;a href="https://listcheckup.com/catch-all-domain" rel="noopener noreferrer"&gt;why catch-all domains can't be verified&lt;/a&gt;, and a 54-second video overview &lt;a href="https://youtu.be/yLZPOJNrIRQ" rel="noopener noreferrer"&gt;on YouTube&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you've found other ways to handle catch-all segments, I'd love to hear them in the comments.&lt;/p&gt;

</description>
      <category>email</category>
      <category>webdev</category>
      <category>marketing</category>
      <category>saas</category>
    </item>
  </channel>
</rss>
