<?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: Francisco Perez</title>
    <description>The latest articles on DEV Community by Francisco Perez (@francofuji).</description>
    <link>https://dev.to/francofuji</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%2F3611267%2F25769676-92b1-4493-b269-7b75a6f07f5f.jpg</url>
      <title>DEV Community: Francisco Perez</title>
      <link>https://dev.to/francofuji</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/francofuji"/>
    <language>en</language>
    <item>
      <title>The App That Remembers Your Doctor's Appointment and Forgets Your Name</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Wed, 30 Sep 2026 16:41:44 +0000</pubDate>
      <link>https://dev.to/francofuji/the-app-that-remembers-your-doctors-appointment-and-forgets-your-name-3kep</link>
      <guid>https://dev.to/francofuji/the-app-that-remembers-your-doctors-appointment-and-forgets-your-name-3kep</guid>
      <description>&lt;p&gt;If you're hunting for a business idea, listen: this one could be worth as much as ten million dollars a year. The good news for you is that almost every app chasing it is built around the same blind spot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this a real business, or is it smoke? Let's find out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm Onald Grump. I tear apart business ideas, one a week. This week I went through thirteen research pages, six Reddit communities, and a keyword growing 12,000%, so you don't have to. Here's the plan: I'll show you what the product does, who pays, and how much. Then I'll show you the one part the pitch would rather you skipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meet Daniel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;His father, Ray, tells the same story every visit: his first job, a bakery, four in the morning, flour up to the elbows.&lt;/p&gt;

&lt;p&gt;Daniel records some of it. Voice memos named New Recording 12, New Recording 13. Then the phone gets replaced, and the memos are somewhere Daniel can no longer find.&lt;/p&gt;

&lt;p&gt;Daniel's plan was to sort them this weekend. Daniel has been sorting them this weekend for a very long time.&lt;/p&gt;

&lt;p&gt;The recordings exist. The love exists. The folder does not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8esbs0adfxt88ztdgpgj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8esbs0adfxt88ztdgpgj.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why now&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dementia care apps are projected to grow 13–16.5% a year, and the U.S. market alone goes from $1.5B to $4.2B by 2033. Those are the research's own figures, not mine, so treat them as estimates.&lt;/p&gt;

&lt;p&gt;Look at what exists. Apps like CareZone track medications, appointments, and symptoms. Then they stop. Who the person is, their stories, their voice — all of that lives outside the care stack. Very few tools even try to combine daily orientation, comfort, and story capture.&lt;/p&gt;

&lt;p&gt;And people are already searching next door. "FamilySearch family tree": 165,000 searches a month, up 12,592%. "Search family history": 33,000, up 4,497%. Those are genealogy searches, not dementia searches, so call it adjacent demand. My own theory: "clean up iPhone storage," up 410%. The phone is full, and the stories are in there.&lt;/p&gt;

&lt;p&gt;And this is what gets me: these apps know a medication schedule down to the minute. They know Tuesday's appointment. They do not know what he called the street he grew up on. Every one of these products remembers the appointment and forgets the person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The product&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's called Keepsong. That's the name on the front page of the research, though the sub-pages call it Memory Keeper — I'm going with Keepsong.&lt;/p&gt;

&lt;p&gt;Every morning, a check-in shows the date, the weather, and photos of family with their names and how they're connected. Then a prompt — tell me about your wedding — and the answer is a voice recording. Those recordings sort themselves into a family timeline by decade and topic. The adult kids end up with a growing library of a parent's voice and life.&lt;/p&gt;

&lt;p&gt;Version one is an iPhone app with voice recording and photo upload, and 20 beta families to learn which prompts get the richest answers. Two screens: one for the person living with memory loss, one for the family member who sets everything up. That split is the core design problem, by the research's own admission — we'll come back to that.&lt;/p&gt;

&lt;p&gt;The bigger swings come later: printed memoir books made from the recordings, and voice cloning, so late in the illness a family could still hear a parent speak. I have thoughts on that one. Also later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who pays&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adult children, roughly 35–60, juggling a job and a parent. Not the person with dementia — the person who is afraid of losing the recordings.&lt;/p&gt;

&lt;p&gt;Do they exist? Reddit's r/Genealogy has 201,000 members. r/Ancestry, 25,000. r/cogsci, 108,000. And r/Alzheimers — the community that actually lives this? 5,000. The hobbyists are huge. The people with the problem are small, or somewhere else. The research also lists caregiver Facebook groups, sizes unknown.&lt;/p&gt;

&lt;p&gt;The research projects a customer acquisition cost of $15–25 and churn under 5%. Those are projections. And under 5% is doing a lot of work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The money&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now the money. A free trial to get you in. $9.99/month for basic: daily orientation, prompts, simple recording. $19.99 for the enhanced tier, with personalized prompts and more storage. $29.99/month for the family collaboration hub.&lt;/p&gt;

&lt;p&gt;And the backend: a $5,000 annual license for memory care centers. My math, not theirs: one license is about 42 basic subscribers paying for a full year. That's the real B2B door.&lt;/p&gt;

&lt;p&gt;The research puts the revenue potential at $1–10 million a year. That's an estimate, not a promise. I have seen many estimates make that same confident face.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The competitor named in the research is CareZone, and it doesn't do this. The bigger threat is the established elder-care apps that can bolt on a story feature quickly. And next door sit FamilySearch, Ancestry, and MyHeritage, who already own the family-story audience. Nobody here is empty. They just haven't looked over.&lt;/p&gt;

&lt;p&gt;The value equation scores this a 6/10. Dream outcome: 8. Likelihood: 6. Time delay: 6. Effort: 5. Effort is the number to stare at.&lt;/p&gt;

&lt;p&gt;Here's the thing I said we'd come back to: the person on the memory-loss side has to open this every morning, on their own, and the whole app depends on it being simple enough to use alone. The research lists older-user engagement as a risk. That's the entire business in one sentence.&lt;/p&gt;

&lt;p&gt;And the material is medical and deeply personal. Encryption from day one, the research says, and it should. Add voice cloning of a parent and the question isn't can you build it. It's who agreed, and to what. That's not a feature question. That's a trust question, and trust is the product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ixttbivypqa1wnt4ah8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ixttbivypqa1wnt4ah8.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The scorecard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Problem — 9 Willingness to pay — 6 Ease to build — 6 Ease to get customers — 7 Competition — 7 Solo-founder fit — 5&lt;/p&gt;

&lt;p&gt;Feasibility is a 6/10, and the research wants a technical founder with a $10–50K budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict: Business&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;$9.99 a month to keep a voice. But only if it opens on its own, and a family can trust it with a parent's story.&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;Every week I tear apart one business idea like this — the research, the numbers, the part the pitch would rather you skipped. If this one was useful, the full breakdown (with all the receipts) is on YouTube: &lt;a href="https://youtu.be/yTyAqjbZ4OA" rel="noopener noreferrer"&gt;https://youtu.be/yTyAqjbZ4OA&lt;/a&gt;&lt;/p&gt;

</description>
      <category>startup</category>
      <category>entrepreneurship</category>
      <category>indiehackers</category>
      <category>caregiving</category>
    </item>
    <item>
      <title>Address Space Exhaustion in Production</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:55:16 +0000</pubDate>
      <link>https://dev.to/francofuji/address-space-exhaustion-in-production-2797</link>
      <guid>https://dev.to/francofuji/address-space-exhaustion-in-production-2797</guid>
      <description>&lt;p&gt;I went looking for why none of our registered users were using the product. I found something worse: for most of them, the product had simply stopped working. Address space exhaustion had been quietly breaking mailbox creation for weeks, and not one of our monitoring tools said so. One of them actively told us the opposite.&lt;/p&gt;

&lt;p&gt;This is the writeup of that outage. The bug itself is almost embarrassingly simple — a combinatorial space that filled up because nothing ever left it. The interesting part is the twenty minutes I spent believing my instruments over my arithmetic, and what that says about how these failures hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  A generator with 36,000 possible outputs
&lt;/h2&gt;

&lt;p&gt;We hand out disposable email addresses. They're meant to be readable and quotable over a phone call, so they aren't random hashes — they're &lt;code&gt;adjective-noun-NN@domain&lt;/code&gt;. The generator was about as simple as it looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_ADJECTIVES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mango&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coral&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verde&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;  &lt;span class="c1"&gt;# 20 entries
&lt;/span&gt;&lt;span class="n"&gt;_NOUNS&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;panda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tigre&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;koala&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;  &lt;span class="c1"&gt;# 20 entries
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_friendly_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Genera una direccion amigable del tipo &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mango-panda-42@dominio.com&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.    Hay 20x20x90 = 36 000 combinaciones posibles.
    Si hay colision en DB, el router reintenta (max 5 veces).
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;adjective&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_ADJECTIVES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;noun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_NOUNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;adjective&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noun&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;@&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;Note&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;docstring&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Past&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;did&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;multiplication&lt;/span&gt; &lt;span class="n"&gt;correctly&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;wrote&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;shipped&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;anyway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;never&lt;/span&gt; &lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;It&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;sitting&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Spanish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;three&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="n"&gt;above&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt; &lt;span class="n"&gt;would&lt;/span&gt; &lt;span class="n"&gt;eventually&lt;/span&gt; &lt;span class="n"&gt;take&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;creation&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="n"&gt;handled&lt;/span&gt; &lt;span class="n"&gt;collisions&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;obvious&lt;/span&gt; &lt;span class="n"&gt;way&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;whether&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;does&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;again&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;five&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_MAX_ADDRESS_RETRIES&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="c1"&gt;# 5
&lt;/span&gt;    &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_friendly_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;address&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="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scalar_one_or_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ... insert and break
&lt;/span&gt;        &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mailbox&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No se pudo generar una direccion unica.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five attempts is plenty when the space is 5% full. It is not plenty at 93%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why address space exhaustion is invisible until it isn't
&lt;/h2&gt;

&lt;p&gt;Here is the shape of this class of bug, and why it gives you no warning.&lt;/p&gt;

&lt;p&gt;The probability that a single request fails is the probability that all five candidates collide: &lt;code&gt;occupancy^5&lt;/code&gt;. At 50% occupancy that's 3%. At 80%, still only 33%. At 90% it's 59%. At 93.3% — where we actually were, with 33,590 of 36,000 addresses taken — it's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.933^5 = 0.707  -&amp;gt;  ~70% of requests should fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That curve is the whole story. For months the failure rate is a rounding error, then it goes vertical. There is no gentle degradation to alert on, no slow climb anyone notices in a weekly review. The system is fine, fine, fine, and then it is 70% broken.&lt;/p&gt;

&lt;p&gt;And when I measured it live against production — just firing ten requests at the endpoint — nine of ten came back &lt;code&gt;503&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lie #1: the analytics said 18 errors
&lt;/h2&gt;

&lt;p&gt;Our frontend emits a &lt;code&gt;funnel_error&lt;/code&gt; event when a step fails. Querying it for the whole history returned &lt;strong&gt;18&lt;/strong&gt; errors of this type, spread over six weeks. Eighteen is a number you look at and move on from. Eighteen is noise.&lt;/p&gt;

&lt;p&gt;The API had returned hundreds of 503s over the same period.&lt;/p&gt;

&lt;p&gt;The gap wasn't a bug in the event pipeline. It was that &lt;code&gt;funnel_error&lt;/code&gt; only fires on certain frontend paths — the ones where a component was wired to report it. Anything that failed outside those paths (API clients, the MCP server, the retry that happened before the component mounted) failed silently as far as analytics was concerned.&lt;/p&gt;

&lt;p&gt;This is the lesson I'd generalize: &lt;strong&gt;product analytics measures what you instrumented, which is a subset of what your users did, which is a subset of what your system did.&lt;/strong&gt; It is a fine tool for asking "did people click this?" It is not a health signal, and the moment you treat it as one you've built a monitor whose blind spots exactly match the code paths nobody thought about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lie #2: the container logs said 4%
&lt;/h2&gt;

&lt;p&gt;Analytics undercounting is at least an honest kind of wrong — it never claimed to be complete. The second tool was worse, because it gave me a confident, specific, wrong answer.&lt;/p&gt;

&lt;p&gt;I pulled the request log out of the container and counted status codes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs uct_app &lt;span class="nt"&gt;--tail&lt;/span&gt; 100000 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"POST /api/v1/mailboxes"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oE&lt;/span&gt; &lt;span class="s2"&gt;"(503|201)"&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;13451 201
  314 503
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 2.3% failure rate. I narrowed to the most recent requests — last 3000, last 1000, last 200 — and got 3.7%, 4.7%, 4%. Consistent, plausible, and completely wrong. The real rate at that moment was around 90%.&lt;/p&gt;

&lt;p&gt;What happened is that &lt;code&gt;--tail N&lt;/code&gt; did not return the actual tail. With log rotation in play, the window I was slicing wasn't the window I thought I was slicing, so the lines I was confidently calling "the last 200 requests" were nothing of the sort.&lt;/p&gt;

&lt;p&gt;I want to be precise about the failure mode here, because "logs lied" is too glib. The log file wasn't corrupt. Every line in it was true. The command returned a real slice of real data — it just wasn't the slice I asked for, and nothing in the output said so. A tool that returns plausible wrong data with no error is far more dangerous than one that fails loudly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trusting the instrument over the arithmetic
&lt;/h2&gt;

&lt;p&gt;Here's the part I got wrong, and it cost me most of the debugging time.&lt;/p&gt;

&lt;p&gt;I had two sources. One was a simulation: I pulled every occupied address out of the database, generated 20,000 candidate addresses using the exact production generator, and applied the exact five-retry logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ocupadas en DB: 33590
espacio total : 36000
libres        : 2410
fallo simulado: 70.4% de 20000 peticiones (5 reintentos)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other was the log, saying 4%.&lt;/p&gt;

&lt;p&gt;These cannot both be true, and I spent a long stretch assuming the simulation was the thing that must be wrong. I re-checked the word lists. I diffed the deployed generator against my local copy in case the container was running older code. I checked whether multiple domains were splitting the namespace. I verified the enum of every adjective and noun actually present in the database. All of it came back confirming the model: 36,000 slots, 33,590 taken, 2,410 free.&lt;/p&gt;

&lt;p&gt;I was interrogating the calculation because the calculation was the thing I could inspect. The log was just... output. It felt like data in a way the model didn't.&lt;/p&gt;

&lt;p&gt;That instinct was backwards. The arithmetic was four lines long and independently verifiable. The log was the output of a distributed system, a container runtime, a rotation policy, and a CLI flag whose semantics I had never actually read. &lt;strong&gt;The simple, checkable thing was the trustworthy one.&lt;/strong&gt; When a model and a measurement disagree, "which of these has more moving parts I haven't verified?" is a better question than "which of these feels more like a fact?"&lt;/p&gt;

&lt;p&gt;What finally settled it was refusing to arbitrate between the two and going to get a third source: ten real HTTP requests against production. Nine failed. The model had been right the whole time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The root cause: nothing was ever released
&lt;/h2&gt;

&lt;p&gt;Filling 93% of a 36,000-address space requires either enormous traffic or a leak. It was a leak.&lt;/p&gt;

&lt;p&gt;Mailboxes are disposable. They expire — most within an hour. A background loop handled expiry like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_expire_mailboxes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Marca is_active=False en buzones expirados.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncSessionLocal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowcount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It marks. It never deletes.&lt;/p&gt;

&lt;p&gt;Meanwhile the uniqueness check in the creation path was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;is_active&lt;/code&gt; filter — and correctly so, because an address that was used an hour ago shouldn't be reissued to a different person while mail might still arrive for it. But the consequence is that &lt;strong&gt;an address is consumed permanently the first time it's handed out.&lt;/strong&gt; Expiry freed the mailbox. It never freed the name.&lt;/p&gt;

&lt;p&gt;The numbers made this vivid once I looked: of 33,590 mailboxes in the table, &lt;strong&gt;5 were alive&lt;/strong&gt;. The other 33,585 were expired, inert, and still holding their address hostage. The oldest had expired on March 2nd — six months earlier.&lt;/p&gt;

&lt;p&gt;There was also a human cost sitting in the data. One user registered, tried to create a mailbox 38 seconds later, got a 503, tried again with a different TTL, got another 503, and never came back. That's the entire lifetime of that account: sign up, hit a wall twice, leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Three changes, in order of how much they matter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purge expired mailboxes.&lt;/strong&gt; This is the actual fix; everything else buys time. A retention window — 72 hours, long enough that a user who wanders back can still read their mail — after which the row is deleted and the address returns to the pool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAILBOX_RETENTION_HOURS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAILBOX_RETENTION_HOURS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;72&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_purge_expired_mailboxes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MAILBOX_RETENTION_HOURS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncSessionLocal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Mailbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cutoff&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="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowcount&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Widen the space.&lt;/strong&gt; The numeric suffix went from two digits to four: 20 x 20 x 9,000 = &lt;strong&gt;3,600,000&lt;/strong&gt; combinations, a 100x increase for a one-line change and no loss of readability (&lt;code&gt;suave-delfin-1608&lt;/code&gt; reads exactly as well as &lt;code&gt;suave-delfin-16&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raise the retry ceiling&lt;/strong&gt; from 5 to 10. Cheap insurance that does nothing on its own — at 93% occupancy, ten retries still fails half the time. Widening the space without purging would have bought about ten days at our traffic. Purging without widening would have worked, but with no headroom for a traffic spike. Both together give a steady state around 10,000 occupied addresses: 0.3% of the new space.&lt;/p&gt;

&lt;p&gt;We also ran a one-off purge of the backlog: &lt;strong&gt;33,596 mailboxes&lt;/strong&gt; deleted, cascading to 12,982 messages, all of them long expired. Occupancy went from 93.3% to 0.005%, and a burst of 30 creation requests came back 30 for 30.&lt;/p&gt;

&lt;h2&gt;
  
  
  Address space exhaustion is not an email problem
&lt;/h2&gt;

&lt;p&gt;Any system that hands out human-readable identifiers from a finite pool has this bug latent in it. Invite codes. URL slugs. Short links. Room codes for a video call. Coupon codes. Anything where somebody chose a format because it reads well, computed the combination count once, and wrote it in a comment.&lt;/p&gt;

&lt;p&gt;Three things make it dangerous:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The failure curve is a cliff, not a slope.&lt;/strong&gt; With &lt;code&gt;k&lt;/code&gt; retries, the failure rate is &lt;code&gt;occupancy^k&lt;/code&gt;, which is negligible right up until it isn't. There's no gradual signal to alert on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pool usually leaks.&lt;/strong&gt; Soft-deletes, tombstones, audit requirements, and "we might need it later" all mean identifiers get retired without being released. Check whether your uniqueness constraint and your lifecycle agree about when an ID is free — ours disagreed for six months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generic error codes hide it.&lt;/strong&gt; A 503 from an exhausted namespace looks identical to a 503 from a database hiccup. Ours said "No se pudo generar una direccion unica" in the response body and nobody was reading response bodies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you have such a pool, two things are worth doing this week: compute your current occupancy as a percentage, and confirm that something actually deletes retired identifiers. Both are ten-minute queries. Ours would have caught this in March.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd take away
&lt;/h2&gt;

&lt;p&gt;The bug was trivial and the arithmetic was in a docstring from day one. What made it an outage was that every instrument pointed away from it — analytics because it only measured instrumented paths, container logs because a flag didn't mean what I assumed, and my own judgment because I trusted output that looked like data over a calculation I could verify in four lines.&lt;/p&gt;

&lt;p&gt;The habit I'm keeping from this one is cheap: when two sources disagree, don't arbitrate between them. Go get a third that touches the real system. Ten curl requests settled in thirty seconds what I'd spent twenty minutes reasoning about, and they'd have settled it on day one just as well.&lt;/p&gt;

&lt;p&gt;If you want to see what the service actually does when it isn't returning 503s, it's at &lt;a href="https://uncorreotemporal.com?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=address-space-exhaustion" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>postmortem</category>
      <category>observability</category>
      <category>debugging</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Email Verification Bots with Playwright</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Thu, 16 Jul 2026 04:00:54 +0000</pubDate>
      <link>https://dev.to/francofuji/email-verification-bots-with-playwright-1dc7</link>
      <guid>https://dev.to/francofuji/email-verification-bots-with-playwright-1dc7</guid>
      <description>&lt;p&gt;If you've ever tried to test your own signup flow, provision a demo account, or sign up for a vendor's API on behalf of your one-person company, you've hit the same wall every solo founder hits: the form submits fine, and then the service says "check your email to verify your account." Now a human has to open an inbox, find the code or the link, and come back to finish the job. That single step is why so many "fully automated" onboarding tests still have a person babysitting them.&lt;/p&gt;

&lt;p&gt;You don't need to be an engineer to fix this. This guide shows how to build a registration bot with Claude Code — the AI coding agent — where Playwright handles the browser and email verification is solved by a temporary inbox the bot owns. You describe what you want in plain English; Claude Code writes and runs the Playwright automation and the email lookups for you. No prior coding experience required, no manual "please check your inbox" step left in the loop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why email verification breaks no-code registration bots
&lt;/h2&gt;

&lt;p&gt;Most no-code and low-code automation tools are genuinely good at the mechanical parts of signing up for something: filling a name field, generating a password, clicking submit. Where they consistently fall apart is the moment the target service says "we sent you an email."&lt;/p&gt;

&lt;p&gt;Three things go wrong when solo founders try to work around this without dedicated infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your personal inbox becomes the bottleneck.&lt;/strong&gt; If the bot uses your real email address, you have to manually forward or copy the OTP every single time. That's not automation — that's you, with extra steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared or reused addresses get flagged.&lt;/strong&gt; Many signup forms silently reject addresses that look reused, disposable, or machine-generated. If your bot reuses &lt;code&gt;test@yourdomain.com&lt;/code&gt; for every run, some services will start bouncing it after a few signups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading a real inbox by code is a project of its own.&lt;/strong&gt; Wiring up Gmail or Outlook programmatically means OAuth, token refresh, and app permissions — a serious side quest if all you wanted was to test a signup form.&lt;/p&gt;

&lt;p&gt;The fix that professional QA teams use is the same one that works for a solo founder with no engineering background: give the bot a fresh, disposable inbox for every run, one it can create and read on its own. That's what turns "check your email" from a manual step into something Claude Code can just... do.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Claude Code drives Playwright for email verification
&lt;/h2&gt;

&lt;p&gt;Here's the mental model, without any code required on your end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playwright&lt;/strong&gt; is the browser automation engine. It's what actually opens a Chromium browser, navigates to the signup page, types into form fields, and clicks buttons — the same way a human would, just faster and unattended. You never touch Playwright directly; you describe the task, and Claude Code writes and runs the Playwright script behind the scenes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The temporary email layer&lt;/strong&gt; is what closes the loop that Playwright alone can't handle. Playwright can click "Create account," but it has no way to open an inbox and read a verification code — browsers don't have mailboxes. That's the job of the &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;UnCorreoTemporal MCP server&lt;/a&gt;, which gives Claude Code a set of tools purpose-built for exactly this handoff: &lt;code&gt;create_signup_inbox&lt;/code&gt;, &lt;code&gt;wait_for_verification_email&lt;/code&gt;, &lt;code&gt;extract_otp_code&lt;/code&gt;, &lt;code&gt;extract_verification_link&lt;/code&gt;, and &lt;code&gt;complete_signup_flow&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Put together, the architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You describe the task in plain English
        ↓
Claude Code creates a disposable inbox (create_signup_inbox)
        ↓
Claude Code drives Playwright to fill and submit the signup form
        ↓
Claude Code waits for the verification email (wait_for_verification_email)
        ↓
Claude Code extracts the OTP or link (extract_otp_code / extract_verification_link)
        ↓
Claude Code drives Playwright to enter the code or open the link
        ↓
Account verified — inbox expires automatically
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two pieces — Playwright for the browser, MCP for the inbox — never need to know about each other's internals. Claude Code is the one thing coordinating both, which is exactly why you can describe the whole flow in a sentence instead of writing an integration between two separate systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting it up (one command, no code)
&lt;/h3&gt;

&lt;p&gt;You need two things installed once: &lt;a href="https://claude.com/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; itself, and the email MCP server. The second one is a single terminal command — you paste it in once and never think about it again:&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 uncorreotemporal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;UCT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;uct_your_key_here &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; uvx uncorreotemporal-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grab your API key from &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt; first — the free plan is enough to build and test a registration bot end to end. Once the server is registered, restart Claude Code and you're ready. No package installs, no config files to hand-edit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inside a Playwright email verification run
&lt;/h2&gt;

&lt;p&gt;You don't have to write the Playwright code, but seeing what Claude Code actually produces makes it much easier to trust — and to ask for changes when something isn't quite right.&lt;/p&gt;

&lt;p&gt;When you tell Claude Code something like &lt;em&gt;"Sign up for app.example.com using a temporary email, then verify the account,"&lt;/em&gt; it first asks the email MCP server for an inbox:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;create_signup_inbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"service_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.example.com"&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="err"&gt;Output:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-15T11:30:00Z"&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;Then it writes and runs a short Playwright script to fill out the actual form — this is the part happening in the browser, invisible to you unless you ask for a screenshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;register with temporary inbox&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;page&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://app.example.com/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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#email&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="s1"&gt;mx_abc123@mail.uncorreotemporal.com&lt;/span&gt;&lt;span class="dl"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#name&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="s1"&gt;Alex Founder&lt;/span&gt;&lt;span class="dl"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#password&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="s1"&gt;TempP@ss2026!&lt;/span&gt;&lt;span class="dl"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button[type="submit"]&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the form submitted, Claude Code switches back to the email tools and waits — this call blocks internally until the email shows up, so nothing is polling in a loop burning your time or budget:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wait_for_verification_email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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="err"&gt;Output:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify your account"&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;Then it pulls the code or link out of that email:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract_otp_code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.com"&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="err"&gt;Output:&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;"otp_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"847291"&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;And finishes the loop by having Playwright type that code into the verification field and submit it. From the moment you typed your instruction to a verified account, nothing required you to open an inbox, copy a code, or paste anything by hand.&lt;/p&gt;

&lt;p&gt;For the common case — one signup, one verification email — you don't even need the individual steps above. Claude Code can call a single combined tool, &lt;code&gt;complete_signup_flow&lt;/code&gt;, that creates the inbox, waits for the email, and extracts both the OTP and the link in one shot. Ask for the simple version first; only ask for the step-by-step breakdown if you need to filter by subject line or debug why an email isn't showing up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real business uses for a registration bot
&lt;/h2&gt;

&lt;p&gt;This isn't just a party trick — it solves real, recurring friction for a one-person company.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing your own signup flow after every change.&lt;/strong&gt; If you're building a SaaS product yourself (with AI-assisted coding, no-code tools, or a mix), you change your signup form more often than you'd like to admit. A registration bot lets you verify — in under a minute — that new users can still sign up and verify their account, without you manually creating and deleting test accounts all day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provisioning demo or sandbox accounts for vendors.&lt;/strong&gt; Almost every API, payment processor, or B2B tool you evaluate as a solo founder requires its own signed-up, email-verified account. When you're comparing five vendors, that's five signup forms and five inboxes to babysit. The bot does all five while you do something else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onboarding QA before a launch or a marketing push.&lt;/strong&gt; Before you send traffic to a new landing page or signup flow, you want confidence the whole path — form, email, verification — actually works. Running the bot a few times against a staging environment catches broken links or slow email delivery before your first real user does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recurring checks without a QA team.&lt;/strong&gt; You don't have engineers on staff to write and maintain end-to-end tests. Describing the flow to Claude Code once, then re-running it whenever you ship a change, gets you most of the benefit of a QA process without hiring for it.&lt;/p&gt;

&lt;p&gt;In every case, the pattern is the same: Playwright handles "act like a person filling out a form," the temporary inbox handles "receive and read the email a person would have opened," and Claude Code is the one thing tying the two together based on what you asked for in plain language.&lt;/p&gt;




&lt;h2&gt;
  
  
  A quick note on responsible use
&lt;/h2&gt;

&lt;p&gt;This kind of bot is built for testing your own product, evaluating vendors, and running QA — not for creating accounts to bypass a service's free-trial limits or terms of service. Use a short-lived inbox for the specific task you're testing, not to spin up dozens of accounts on someone else's platform.&lt;/p&gt;




&lt;p&gt;Email verification used to be the one step in a registration flow that forced a human to sit and wait. With Claude Code driving Playwright on one side and a disposable inbox on the other, that step disappears — you describe the signup once, and the bot handles the browser, the wait, and the inbox itself. If you want to try it on your own signup flow, &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt; has a free tier that's enough to build and run your first registration bot today.&lt;/p&gt;

&lt;p&gt;Originally published at uncorreotemporal.com/blog/playwright-email-verification-registration-bot&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>claudecode</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Vibe coding y email: el muro invisible</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Tue, 23 Jun 2026 01:01:37 +0000</pubDate>
      <link>https://dev.to/francofuji/vibe-coding-y-email-el-muro-invisible-6mo</link>
      <guid>https://dev.to/francofuji/vibe-coding-y-email-el-muro-invisible-6mo</guid>
      <description>&lt;h2&gt;
  
  
  El momento exacto en que todo se detiene
&lt;/h2&gt;

&lt;p&gt;"Vibe coding" fue la palabra del año 2026 según Collins Dictionary. El mercado de plataformas para construir con IA vale 4.7 billones de dólares y se proyecta a 12.3 billones en 2027. El 46% de todo el código nuevo ya lo genera una IA.&lt;/p&gt;

&lt;p&gt;Y la semana del 15 al 19 de junio, miles de personas terminaron el curso gratuito "AI Agents Vibe Coding" de Google y Kaggle. Muchos de ellos intentaron construir su primer agente esa misma semana.&lt;/p&gt;

&lt;p&gt;La mayoría llegó al mismo muro.&lt;/p&gt;

&lt;p&gt;Llevas dos horas con Claude Code o Cursor. La app funciona. El agente navega pantallas, rellena formularios, llama APIs. Estás en modo vibe coding puro - describes lo que quieres, el agente lo hace.&lt;/p&gt;

&lt;p&gt;Hasta que llega este momento.&lt;/p&gt;

&lt;p&gt;El agente necesita registrarse en algún servicio - un proveedor de datos, una API de terceros, una plataforma de pagos en modo sandbox. Rellena el formulario. Hace clic en "Crear cuenta". Y después... nada.&lt;/p&gt;

&lt;p&gt;El servicio envió un correo de verificación. Tu agente no tiene buzón de email. No puede recibirlo. No puede continuar.&lt;/p&gt;

&lt;p&gt;Este es el muro invisible que paraliza a casi todo agente de vibe coding en algún momento. No es un bug de tu código. No es un error del modelo. Es infraestructura que falta.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por qué el agente no puede manejar esto solo
&lt;/h2&gt;

&lt;p&gt;Cuando un servicio envía un correo de verificación, está haciendo algo que va fuera del flujo sincrónico normal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tu agente hace una petición HTTP - el servicio responde con "te enviamos un email"&lt;/li&gt;
&lt;li&gt;El email llega a un buzón (cuál buzón? el agente no tiene ninguno)&lt;/li&gt;
&lt;li&gt;Dentro del email hay un enlace o código que hay que usar antes de que expire&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El agente puede razonar sobre pasos 1 y 3 sin problema. El paso 2 es el bloqueo: necesita un buzón de email real que pueda crear, monitorear y leer programáticamente.&lt;/p&gt;

&lt;p&gt;No puedes usar tu email personal (contaminas tu inbox, además de que el agente no puede acceder a él). No puedes inventarte una dirección (el email nunca llega). Y si usas siempre la misma dirección temporal de algún servicio web, el agente no sabe cómo consultar si llegó el email ni cómo extraer el enlace.&lt;/p&gt;

&lt;p&gt;La solución es un buzón temporal creado por código, que el agente pueda crear, esperar a que llegue el email, y leer el contenido - todo vía API o MCP.&lt;/p&gt;




&lt;h2&gt;
  
  
  La solución: UnCorreoTemporal + MCP
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;UnCorreoTemporal&lt;/a&gt; es exactamente eso: una API para crear buzones temporales y leer los emails que llegan. Tiene dos formas de usarlo desde un agente:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opción A - Servidor MCP (recomendada para Claude Code/Cursor):&lt;/strong&gt; El agente tiene herramientas nativas para crear buzones y leer emails. Sin código extra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opción B - REST API directa:&lt;/strong&gt; Si tu agente está en Python, n8n, o cualquier entorno que pueda hacer peticiones HTTP.&lt;/p&gt;

&lt;p&gt;Empezamos con la opción MCP porque es la más rápida de configurar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tutorial: MCP en Claude Code (5 minutos)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Paso 1 - Consigue tu API key
&lt;/h3&gt;

&lt;p&gt;Ve a &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt;, crea una cuenta y copia tu API key. Tiene el formato &lt;code&gt;uct_xxxxxxxx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El plan gratuito incluye 100 buzones por mes - suficiente para la mayoría de side-projects y primeros experimentos. Los planes de pago arrancan en precios pensados para proyectos pequeños, no para empresas: si estás probando tu primera idea de vibe coding, no necesitas gastar más de lo que cuesta un café.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 2 - Registra el servidor MCP
&lt;/h3&gt;

&lt;p&gt;En tu terminal, una sola línea:&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 uncorreotemporal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;UCT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;uct_tu_key_aqui &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; uvx uncorreotemporal-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto le dice a Claude Code que al arrancar, levante el servidor MCP de UnCorreoTemporal. No tienes que instalar nada más - &lt;code&gt;uvx&lt;/code&gt; descarga y ejecuta el servidor automáticamente en un entorno aislado.&lt;/p&gt;

&lt;p&gt;Si prefieres editar el JSON directamente, el bloque equivalente en &lt;code&gt;~/.claude/claude_desktop_config.json&lt;/code&gt; es:&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;"uncorreotemporal"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"uncorreotemporal-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;"env"&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;"UCT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uct_tu_key_aqui"&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;h3&gt;
  
  
  Paso 3 - Reinicia Claude Code
&lt;/h3&gt;

&lt;p&gt;Cierra y vuelve a abrir Claude Code. A partir de aquí, el agente tiene acceso a estas herramientas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create_signup_inbox&lt;/code&gt; - crea un buzón temporal con una dirección de email real&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wait_for_verification_email&lt;/code&gt; - espera hasta que llegue el email&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extract_verification_link&lt;/code&gt; - extrae el enlace de verificación del email recibido&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extract_otp_code&lt;/code&gt; - extrae el código numérico si el servicio usa OTP en lugar de enlace&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Paso 4 - Dale instrucciones al agente
&lt;/h3&gt;

&lt;p&gt;Ahora puedes decirle a Claude Code algo como:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Necesito que te registres en [servicio]. Usa UnCorreoTemporal para crear un buzón temporal, completa el registro con esa dirección, espera el email de verificación y completa el proceso."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El agente sabe qué herramientas usar. Crea el buzón, usa la dirección en el formulario de registro, espera el email, extrae el enlace y lo abre - todo solo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tutorial: REST API directa (para cualquier agente o script)
&lt;/h2&gt;

&lt;p&gt;Si no usas Claude Code o prefieres control total, la API es igual de sencilla.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crear un buzón
&lt;/h3&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 https://uncorreotemporal.com/api/v1/mailboxes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer uct_tu_key_aqui"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Respuesta:&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;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aguila-veloz-42@uncorreotemporal.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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mbx_abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-22T23:00:00Z"&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;Usa &lt;code&gt;address&lt;/code&gt; como email en el formulario de registro del servicio que quieres automatizar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Esperar y leer el email
&lt;/h3&gt;

&lt;p&gt;Después de enviar el formulario, consulta el buzón hasta que llegue algo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uct_tu_key_aqui&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ADDRESS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aguila-veloz-42@uncorreotemporal.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Polling simple - espera hasta 90 segundos
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://uncorreotemporal.com/api/v1/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ADDRESS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Leer el cuerpo completo del primer email
&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://uncorreotemporal.com/api/v1/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ADDRESS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# o email["body_html"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Desde aquí, extraes el enlace o código con un regex o diciéndole al LLM que lo busque.&lt;/p&gt;




&lt;h2&gt;
  
  
  El flujo completo en un vistazo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agente describe la tarea
       |
create_signup_inbox()  -&amp;gt;  dirección@uncorreotemporal.com
       |
Agente rellena el formulario con esa dirección
       |
wait_for_verification_email()  -&amp;gt;  espera hasta 90s
       |
extract_verification_link()  -&amp;gt;  https://servicio.com/verify?token=...
       |
Agente abre el enlace -&amp;gt; cuenta activada
       |
El buzón expira automáticamente (sin basura en tu inbox)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El email nunca llega a ningún buzón personal. El buzón temporal se borra solo cuando expira. No hay rastro.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cuándo usar esto (y cuándo no)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Úsalo cuando:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tu agente necesita registrarse en servicios de terceros para testing o automatización&lt;/li&gt;
&lt;li&gt;Estás construyendo flows de QA que incluyen verificación por email&lt;/li&gt;
&lt;li&gt;Tu agente hace onboarding de prueba en múltiples plataformas&lt;/li&gt;
&lt;li&gt;Quieres que un script de CI/CD complete un registro real sin intervención humana&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;No lo uses para:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crear cuentas con intención de eludir restricciones de servicios&lt;/li&gt;
&lt;li&gt;Registros masivos en plataformas que prohíben el email temporal en sus términos de servicio&lt;/li&gt;
&lt;li&gt;Producción donde necesitas un buzón permanente (para eso usa tu propio dominio)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Un ejemplo real de la vida vibe coder
&lt;/h2&gt;

&lt;p&gt;Imagina que estás construyendo un agente que tiene que probar la integración de tu app con cinco proveedores de pagos distintos. Cada uno requiere una cuenta de sandbox con email verificado.&lt;/p&gt;

&lt;p&gt;Sin UnCorreoTemporal: registras manualmente cada cuenta, verificas cinco emails, copias las credenciales. 20 minutos mínimo.&lt;/p&gt;

&lt;p&gt;Con UnCorreoTemporal: el agente crea cinco buzones, completa los cinco registros, extrae los cinco enlaces de verificación y almacena las credenciales en un archivo. Tú estás mirando cómo lo hace.&lt;/p&gt;

&lt;p&gt;Eso es vibe coding funcionando de verdad.&lt;/p&gt;




&lt;p&gt;Para empezar, ve a &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt;, consigue tu API key y ejecuta &lt;code&gt;claude mcp add&lt;/code&gt;. El agente hace el resto.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>tutorial</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Registration Bot: Claude Code + Temporary Email</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Tue, 12 May 2026 17:19:14 +0000</pubDate>
      <link>https://dev.to/francofuji/registration-bot-claude-code-temporary-email-52e</link>
      <guid>https://dev.to/francofuji/registration-bot-claude-code-temporary-email-52e</guid>
      <description>&lt;h1&gt;
  
  
  Registration Bot: Claude Code + Temporary Email
&lt;/h1&gt;

&lt;p&gt;Your agent fills the name field, generates a password, clicks "Create account" — and hits the wall: &lt;em&gt;Check your inbox to verify your email.&lt;/em&gt; No API to poll. No webhook to register. The verification code is sitting in an inbox only a human can reach, and the agent loop dies.&lt;/p&gt;

&lt;p&gt;The fix is mechanical: give Claude Code a disposable inbox it owns and can query. This walkthrough covers the full flow — inbox creation, form submission, email polling, OTP and link extraction — using the &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;UnCorreoTemporal MCP server&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why registration automation needs a real inbox
&lt;/h2&gt;

&lt;p&gt;Three failure modes appear reliably when teams try to hardcode or share email addresses in registration bots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel run contamination.&lt;/strong&gt; A shared &lt;code&gt;qa@company.com&lt;/code&gt; inbox receives OTPs from ten concurrent test jobs at once. There is no reliable way to attribute a code to the run that triggered it. Flaky tests follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent rejection.&lt;/strong&gt; Many services validate the email domain at signup and reject known free providers, catch-all addresses, and addresses that pattern-match as machine-generated. A hardcoded address either works everywhere or gets blocked with no recovery path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credential management overhead.&lt;/strong&gt; Reading a real inbox programmatically requires OAuth 2.0, token refresh, scoped permissions, and ongoing maintenance. That cost is disproportionate for a CI job that runs every commit.&lt;/p&gt;

&lt;p&gt;Per-flow inboxes created on demand via &lt;code&gt;create_signup_inbox&lt;/code&gt; eliminate all three. Each run gets a fresh address that lives for the duration of the flow. No shared state, no credential setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This guide assumes you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; installed (&lt;code&gt;npm install -g @anthropic-ai/claude-code&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;uv&lt;/strong&gt; installed for running the MCP server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UCT MCP server already configured&lt;/strong&gt; — full setup at &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;uncorreotemporal.com/en/docs/mcp/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one-line setup 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 uncorreotemporal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;UCT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;uct_your_key_here &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; uvx uncorreotemporal-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm it loaded by running &lt;code&gt;/tools&lt;/code&gt; in a new Claude Code session. You should see &lt;code&gt;create_signup_inbox&lt;/code&gt;, &lt;code&gt;wait_for_verification_email&lt;/code&gt;, &lt;code&gt;extract_otp_code&lt;/code&gt;, &lt;code&gt;extract_verification_link&lt;/code&gt;, and &lt;code&gt;complete_signup_flow&lt;/code&gt; in the list.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Create a temporary inbox via MCP
&lt;/h2&gt;

&lt;p&gt;Before touching the signup form, the agent creates a dedicated inbox. This is always the first tool call:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;create_signup_inbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"service_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.example.com"&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="err"&gt;Output:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-12T11:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.example.com"&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;&lt;code&gt;service_name&lt;/code&gt; is a label for your reference — it doesn't affect routing, but it makes debugging easier when running multiple flows. The returned &lt;code&gt;email&lt;/code&gt; is the address to use in the registration form. The inbox is live immediately after this call; no propagation delay.&lt;/p&gt;

&lt;p&gt;Default TTL is 30 minutes. A complete registration loop runs in under 60 seconds — more than enough headroom.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Navigate to the signup form and fill fields
&lt;/h2&gt;

&lt;p&gt;With the inbox address in context, Claude Code fills the registration form. The exact mechanism depends on the target — browser automation, API call, whatever fits — but the prompt structure is consistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Register a free account on app.example.com using:
- Email: mx_abc123@mail.uncorreotemporal.com
- Name: Alex Developer
- Password: TempP@ss2026!

Fill all required fields and submit the form. Stop after submitting.
Do not proceed to email verification yet.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep registration and email-polling as separate instructions. When registration fails, you want a clean stopping point — not an agent already polling an inbox that will never receive email.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Submit the form and wait for the verification email
&lt;/h2&gt;

&lt;p&gt;Once the form is submitted, the agent waits for the incoming email with a single blocking call:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wait_for_verification_email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;arrived:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Output:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="s2"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;"Verify your account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"from_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noreply@app.example.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;"received_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-12T11:01:34Z"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Timeout:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Output:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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 tool polls internally every 3 seconds. From Claude Code's perspective it is a single call — no repeated invocations consuming context budget. 90 seconds covers the vast majority of transactional email delivery.&lt;/p&gt;

&lt;p&gt;If the target service sends multiple emails at signup, filter to get the right one:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_contains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"verify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"from_contains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"noreply@app.example.com"&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;h2&gt;
  
  
  Step 4 — Extract the confirmation link/OTP and complete registration
&lt;/h2&gt;

&lt;p&gt;Pass the &lt;code&gt;message_id&lt;/code&gt; from the previous step to the appropriate extraction tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For OTP codes:&lt;/strong&gt;&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract_otp_code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.com"&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="err"&gt;Output:&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;"otp_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s2"&gt;"847291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"candidates"&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="s2"&gt;"847291"&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;&lt;strong&gt;For magic links:&lt;/strong&gt;&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract_verification_link&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"preferred_domains"&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="s2"&gt;"app.example.com"&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="err"&gt;Output:&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;"verification_link"&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://app.example.com/verify?token=eyJhbGci..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"candidates"&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="s2"&gt;"https://app.example.com/verify?token=eyJhbGci..."&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://app.example.com/unsubscribe?email=..."&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;&lt;code&gt;preferred_domains&lt;/code&gt; filters out unsubscribe links and tracking URLs that appear in most HTML email. &lt;code&gt;candidates&lt;/code&gt; exposes every URL found — useful when debugging extraction failures.&lt;/p&gt;

&lt;p&gt;Feed the result back to Claude Code with a straightforward instruction: &lt;em&gt;"The OTP is 847291. Submit it in the verification form."&lt;/em&gt; or &lt;em&gt;"Navigate to this URL to confirm the account."&lt;/em&gt; The registration is complete. No human touched it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error handling patterns (slow email delivery, retry logic, timeouts)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Email never arrives (&lt;code&gt;status: timeout&lt;/code&gt;).&lt;/strong&gt; Common causes: slow SMTP relay on the target service, the email filtered as spam before delivery, or a silent rejection at the registration form. Mitigation: increase &lt;code&gt;timeout_seconds&lt;/code&gt; to 120-180 for known slow senders. After a timeout, call &lt;code&gt;get_latest_email&lt;/code&gt; against the inbox — sometimes the email arrives just after the window closes and you can extract the token without re-running the signup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OTP expired before submission.&lt;/strong&gt; Most OTPs expire in 10 minutes. A complete loop runs in well under 60 seconds, so expiry is rarely the issue. If your browser automation is slow and you're hitting this consistently, request a new code from the service before submitting rather than re-running the full flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service blocks the email domain.&lt;/strong&gt; Some platforms maintain blocklists of known disposable providers. If the registration form returns "Please use a valid email address," the domain is blocked at the service level. &lt;a href="https://uncorreotemporal.com/en/pricing/" rel="noopener noreferrer"&gt;Paid plans&lt;/a&gt; support custom domains so the agent receives email at &lt;code&gt;agent@yourdomain.com&lt;/code&gt; instead of the default UCT domain — that bypasses most domain-level blocklists.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making the agent resilient in production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Instruct the agent to stop on failure, not retry automatically.&lt;/strong&gt; A retry spiral burns API quota and can trigger rate limits on the target service. Adding &lt;em&gt;"If any step returns an error or unexpected result, report what happened and stop"&lt;/em&gt; to your agent prompt costs nothing and prevents a class of runaway loops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;complete_signup_flow&lt;/code&gt; for straightforward cases.&lt;/strong&gt; If the target service sends exactly one verification email and you need the OTP or link, skip the individual tool chain:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;complete_signup_flow&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"service_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"app.example.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;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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="err"&gt;Output:&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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"otp_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="s2"&gt;"847291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verification_link"&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://app.example.com/verify?token=..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;"received"&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;This creates the inbox, waits for the email, and runs both extraction tools in a single call. Use the individual tools when you need subject or sender filtering, multi-email flows, or explicit debugging breakpoints between steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Size the TTL to the flow.&lt;/strong&gt; For flows that span multiple emails — activation link, then API key delivery — extend &lt;code&gt;ttl_minutes&lt;/code&gt; at creation time. The default 30 minutes is enough for single-email flows; multi-step flows that take longer need more runway.&lt;/p&gt;

&lt;p&gt;For the complete tool parameter reference and additional patterns for CI pipelines, see the &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;MCP tool reference&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;The email verification wall is a solved problem. &lt;a href="https://uncorreotemporal.com/en/" rel="noopener noreferrer"&gt;Try UnCorreoTemporal free&lt;/a&gt; — no credit card required, and the full agent loop runs in under a minute once the MCP server is configured. The free tier is enough to wire up and validate a complete registration flow before committing it to production.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>mcp</category>
      <category>automation</category>
    </item>
    <item>
      <title>MCP vs REST API for AI Agent Email: When to Use Each</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Fri, 08 May 2026 22:02:18 +0000</pubDate>
      <link>https://dev.to/francofuji/mcp-vs-rest-api-for-ai-agent-email-when-to-use-each-28jf</link>
      <guid>https://dev.to/francofuji/mcp-vs-rest-api-for-ai-agent-email-when-to-use-each-28jf</guid>
      <description>&lt;p&gt;You want your AI agent to receive emails — OTP codes, verification links, signup confirmations. You have two options: call a REST API directly, or connect an &lt;a href="https://uncorreotemporal.com/en/blog/mcp-email-agent-inboxes/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt;. Both work. The trade-offs are not obvious until you've implemented both.&lt;/p&gt;

&lt;p&gt;For agents running in Claude Code or Cursor, MCP almost always wins. But there are real scenarios where REST is the right answer. This article covers both, honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the difference, exactly?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;REST API&lt;/strong&gt;: the agent makes explicit HTTP calls using &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;requests&lt;/code&gt;. It manages the full flow — constructing URLs, setting headers, polling for responses, parsing JSON. The agent "knows" it's calling an API and writes the glue code accordingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP server&lt;/strong&gt;: the agent invokes tools declared by the server — &lt;code&gt;create_inbox()&lt;/code&gt;, &lt;code&gt;wait_for_email()&lt;/code&gt;, &lt;code&gt;extract_otp()&lt;/code&gt; — as if they were native functions. It does not make HTTP calls directly. The host runtime (Claude Code, Cursor) manages the transport layer. The agent reasons about &lt;em&gt;intent&lt;/em&gt;, not plumbing.&lt;/p&gt;

&lt;p&gt;The distinction matters more than it might appear at first glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency and round-trips
&lt;/h2&gt;

&lt;p&gt;The number of round-trips differs significantly between the two approaches.&lt;/p&gt;

&lt;p&gt;With REST, the agent has to orchestrate everything manually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;POST /inboxes/&lt;/code&gt; → get &lt;code&gt;inbox_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /inboxes/{id}/messages/&lt;/code&gt; in a polling loop (every N seconds)&lt;/li&gt;
&lt;li&gt;Parse the email body manually to extract the OTP&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With MCP, the agent invokes three tool calls and the server handles the rest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;create_inbox()&lt;/code&gt; → &lt;code&gt;inbox_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wait_for_email(inbox_id, timeout=60)&lt;/code&gt; → email object (long-polling managed server-side)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extract_otp(email_id)&lt;/code&gt; → numeric code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what that looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# REST approach — agent has to manage polling loop
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="n"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/inboxes/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&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="n"&gt;inbox_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Poll every 3 seconds, up to 60 seconds
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/inboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Parse OTP from HTML body manually
&lt;/span&gt;        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b\d{6}\b&lt;/span&gt;&lt;span class="sh"&gt;'&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="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# MCP approach — agent calls tools, server handles complexity
# Inside the agent loop (Claude Code / Cursor):
# create_inbox() → {"inbox_id": "abc123", "address": "abc123@uct.dev"}
# wait_for_email(inbox_id="abc123", timeout=60) → email object
# extract_otp(email_id="msg_xyz") → "847291"
#
# The agent never writes a polling loop.
# The server handles long-polling, timeouts, and OTP extraction.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The REST version is not complicated — but it requires the agent to reason about polling intervals, handle empty responses, and parse unstructured text. Each of those is a place where something can go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool call ergonomics and context usage
&lt;/h2&gt;

&lt;p&gt;This is the most important MCP advantage for AI agents specifically.&lt;/p&gt;

&lt;p&gt;With REST, the agent consumes context tokens describing what URL to call, which headers to include, how to handle the response, and what to do if the request fails. Every step is explicit in the context window.&lt;/p&gt;

&lt;p&gt;With MCP, the agent declares intent: &lt;code&gt;create_inbox&lt;/code&gt;, &lt;code&gt;wait_for_email&lt;/code&gt;. The server implements the details. The agent's context stays focused on the task, not on HTTP plumbing.&lt;/p&gt;

&lt;p&gt;For long-running agents with limited context windows, this is not a minor concern. An agent handling a 20-step workflow that includes email verification has a meaningfully smaller context footprint when email is an MCP tool versus a sequence of raw HTTP calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging experience
&lt;/h2&gt;

&lt;p&gt;With REST, when something fails the agent sees an HTTP status code and a response body. It has to reason about what to do with a &lt;code&gt;429 Too Many Requests&lt;/code&gt; or a &lt;code&gt;504 Gateway Timeout&lt;/code&gt; — write retry logic, decide when to give up, handle partial failures.&lt;/p&gt;

&lt;p&gt;With MCP, errors are returned as structured tool results. The agent runtime handles them uniformly. Retry logic lives in the server, not scattered across agent code.&lt;/p&gt;

&lt;p&gt;There's also a tooling advantage: MCP calls are visible in Claude.ai and in the MCP Inspector. You can watch exactly what the agent called and what came back. With REST you need a proxy or server-side logs to get the same visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  When REST API wins
&lt;/h2&gt;

&lt;p&gt;Being honest here is what makes this comparison useful. REST is the right choice in several real scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Integration with systems that don't support MCP&lt;/strong&gt;: n8n, Zapier, GitHub Actions, bash scripts. If your "agent" is an automation workflow, REST is the only practical option. MCP requires a host that speaks the protocol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Multiple languages in the same pipeline&lt;/strong&gt;: if a Go backend and a Python script need to share the same inbox, REST coordinates that cleanly. MCP is designed for a single agent runtime, not cross-language pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Granular retry logic&lt;/strong&gt;: if you need custom exponential backoff, dead-letter queues, or specific failure semantics, REST gives you full control. MCP abstracts those decisions to the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Webhooks and async delivery&lt;/strong&gt;: if you need to receive emails asynchronously without the agent being active, the answer is a webhook endpoint on your server calling the REST API. MCP doesn't apply to this pattern — it assumes the agent is present and making calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  When MCP wins
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The agent runs in Claude Code or Cursor&lt;/strong&gt;: these hosts have native MCP support. Configuration is a JSON snippet in &lt;code&gt;claude_desktop_config.json&lt;/code&gt;. No HTTP code to write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The flow requires waiting for an email&lt;/strong&gt;: &lt;code&gt;wait_for_email()&lt;/code&gt; with server-side long-polling is significantly cleaner than a polling loop in the agent. The server blocks until the email arrives (or the timeout fires), and the agent gets back a clean result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Structured extraction&lt;/strong&gt;: when the agent needs specific data from an email — OTP codes, verification links, dates — the server can expose targeted tools that return already-parsed values. The agent never touches raw email body text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Less surface area for failure&lt;/strong&gt;: fewer lines of code in the agent means fewer things that can go wrong. The MCP server owns the implementation complexity; the agent owns the task logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Use MCP&lt;/th&gt;
&lt;th&gt;Use REST API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent runs in Claude Code or Cursor&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation workflow (n8n, Zapier, GitHub Actions)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need to wait for an OTP email&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need webhooks (async, server-push)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple languages in same pipeline&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want minimal agent context usage&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need custom retry/backoff logic&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prototyping quickly with Claude Desktop&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to get started with both
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt;: add three lines to your &lt;code&gt;claude_desktop_config.json&lt;/code&gt;:&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;"uncorreotemporal"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@uncorreotemporal/mcp-server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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;"UCT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_api_key"&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;Restart Claude Desktop or Cursor. The tools are available immediately — no adapter code required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt;: base URL is &lt;code&gt;https://uncorreotemporal.com/api/v1/&lt;/code&gt;. Full reference at &lt;a href="https://uncorreotemporal.com/en/docs/api/" rel="noopener noreferrer"&gt;/en/docs/api/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Both approaches are available on the &lt;a href="https://uncorreotemporal.com/en/pricing/" rel="noopener noreferrer"&gt;free plan&lt;/a&gt; — &lt;a href="https://uncorreotemporal.com/en/" rel="noopener noreferrer"&gt;create an inbox&lt;/a&gt; in under 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For agents in Claude Code or Cursor, MCP is almost always the right answer: less code, less context, cleaner debugging, and the server handles the complexity the agent would otherwise have to manage. For automation pipelines or integrations with systems that don't speak MCP, REST wins — and it's the only option for webhook-based patterns.&lt;/p&gt;

&lt;p&gt;The practical upside: you don't have to choose permanently. UnCorreoTemporal supports both interfaces with the same credentials. Start with whichever fits your current stack, and switch when it makes sense.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>api</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Use Claude Code as an AI Agent with Email Verification</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Wed, 06 May 2026 13:51:58 +0000</pubDate>
      <link>https://dev.to/francofuji/how-to-use-claude-code-as-an-ai-agent-with-email-verification-4b1a</link>
      <guid>https://dev.to/francofuji/how-to-use-claude-code-as-an-ai-agent-with-email-verification-4b1a</guid>
      <description>&lt;p&gt;Your agent is moving fast. It navigates to the registration page, fills in the name field, generates a strong password, clicks "Create account" — and then the response comes back: &lt;em&gt;Check your email to verify your address.&lt;/em&gt; Dead stop.&lt;/p&gt;

&lt;p&gt;There's no API endpoint to poll. No webhook you can register. The verification code is sitting in an inbox that only a human with a browser can reach. So the agent loop breaks, you paste in an OTP manually, and wonder why you're babysitting a process you spent two days automating.&lt;/p&gt;

&lt;p&gt;This is the wall that kills most autonomous registration flows. It's not a reasoning failure — Claude Code made all the right decisions. The problem is that standard email inboxes don't have an API. They weren't built for agents.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://uncorreotemporal.com/en/blog/mcp-email-agent-inboxes" rel="noopener noreferrer"&gt;UnCorreoTemporal MCP server&lt;/a&gt; solves exactly this. It gives Claude Code a set of tools to create disposable inboxes on demand, wait for incoming email, and extract OTPs or verification links — all within the same agent loop, no human in the middle. This guide walks through the full setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;By the end of this guide, Claude Code will be able to run this flow completely autonomously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude Code agent loop
  └─► create_signup_inbox()  ──► generates mx_abc123@mail.uncorreotemporal.com
  └─► register at app.example.com using that address
  └─► wait_for_verification_email() ──► blocks until email arrives (up to 90s)
  └─► extract_otp_code() ──► pulls "847291" from the email body
  └─► submit OTP ──► registration complete ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code acts as the autonomous agent. The UnCorreoTemporal MCP server provides the inbox tooling — &lt;code&gt;create_signup_inbox&lt;/code&gt;, &lt;code&gt;wait_for_verification_email&lt;/code&gt;, &lt;code&gt;extract_otp_code&lt;/code&gt;, &lt;code&gt;extract_verification_link&lt;/code&gt;, and a convenience wrapper called &lt;code&gt;complete_signup_flow&lt;/code&gt; that chains all of them in a single call.&lt;/p&gt;

&lt;p&gt;The target service is whatever you're actually automating. In this guide I'll use &lt;code&gt;app.example.com&lt;/code&gt; as a generic placeholder, but the same pattern applies to any SaaS trial, developer portal, or API sign-up that gates access behind an email confirmation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; installed: &lt;code&gt;npm install -g @anthropic-ai/claude-code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;uv&lt;/strong&gt; installed (for running the MCP server via &lt;code&gt;uvx&lt;/code&gt;): &lt;code&gt;curl -LsSf https://astral.sh/uv/install.sh | sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An UnCorreoTemporal API key&lt;/strong&gt; — free tier at &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt;, 100 inboxes/month, no credit card&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.10+&lt;/strong&gt; — optional, only needed for the standalone script variant in the extraction section&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1 — Configure the MCP Server in claude_desktop_config.json
&lt;/h2&gt;

&lt;p&gt;This is the step most guides skip over. If the MCP server isn't wired up correctly, Claude Code has no email tools and the whole thing is dead before it starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  The quick way: &lt;code&gt;claude mcp add&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Claude Code ships with a CLI command for registering MCP servers:&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 uncorreotemporal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;UCT_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;uct_your_key_here &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; uvx uncorreotemporal-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Claude Code writes the config for you. If you prefer to edit the JSON directly, the equivalent block in &lt;code&gt;~/.claude/claude_desktop_config.json&lt;/code&gt; looks like this:&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;"uncorreotemporal"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"uncorreotemporal-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;"env"&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;"UCT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uct_your_key_here"&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;h3&gt;
  
  
  What this does
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;mcpServers&lt;/code&gt; block tells Claude Code which external tool servers to launch when it starts. Each entry specifies a command to run — in this case &lt;code&gt;uvx uncorreotemporal-mcp&lt;/code&gt;, which downloads and runs the server from PyPI using uv's isolated environment runner — plus any environment variables the server needs.&lt;/p&gt;

&lt;p&gt;The API key goes in &lt;code&gt;env&lt;/code&gt;, not hardcoded in &lt;code&gt;args&lt;/code&gt;. This keeps it out of shell history, out of version control, and makes it easy to rotate without touching the config structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify it loaded
&lt;/h3&gt;

&lt;p&gt;Start a new Claude Code session and type &lt;code&gt;/tools&lt;/code&gt;. You should see a list that includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create_signup_inbox
wait_for_verification_email
get_latest_email
extract_otp_code
extract_verification_link
complete_signup_flow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the tools don't show up, check that &lt;code&gt;uvx&lt;/code&gt; is on your PATH (&lt;code&gt;which uvx&lt;/code&gt;). If it's not, &lt;code&gt;uv&lt;/code&gt; isn't installed or isn't in your shell's PATH — run &lt;code&gt;source $HOME/.local/bin/env&lt;/code&gt; or restart your terminal after installing uv.&lt;/p&gt;

&lt;p&gt;The full MCP tool reference is at &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;uncorreotemporal.com/en/docs/mcp/&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Create a Disposable Inbox
&lt;/h2&gt;

&lt;p&gt;Once Claude Code has the tools loaded, creating an inbox is a single tool call. Here's what Claude Code actually sends and receives:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;create_signup_inbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"service_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.example.com"&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="err"&gt;Output:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-06T10:04:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.example.com"&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;A few things worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;service_name&lt;/code&gt; is required.&lt;/strong&gt; It's a label for your own reference — it doesn't affect routing, but it helps when you're running multiple inboxes and debugging which one belongs to which flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;inbox_id&lt;/code&gt; is the email address.&lt;/strong&gt; There's no separate opaque ID — the email itself is the identifier you pass to subsequent tool calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The inbox is live immediately.&lt;/strong&gt; No DNS propagation, no warming period. Mail sent to that address is queued within seconds of creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default TTL is 30 minutes&lt;/strong&gt;, configurable via &lt;code&gt;ttl_minutes&lt;/code&gt;. For most signup flows, 30 minutes is more than enough — the whole loop should complete in under 60 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude Code stores the returned email address in its context and uses it for the next step.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Register on the Target Service
&lt;/h2&gt;

&lt;p&gt;Now Claude Code takes that address and uses it to sign up. The prompt you give it controls how it does this — browser automation, API call, form fill, whatever fits the target service. For a typical SaaS registration flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Register a free account on app.example.com using the email address from the 
inbox you just created. Fill in all required fields with realistic placeholder 
data. Use the name "Alex Developer" and generate a strong password. Once 
you submit the form, stop and wait for my next instruction.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code will substitute the generated email address into the registration form. Everything else in the form — name, password, company, timezone — it can fill with plausible placeholder data. The email is the only field that must match the inbox.&lt;/p&gt;

&lt;p&gt;This step doesn't require any additional MCP tool calls. Claude Code handles the form interaction through its existing browser automation or API capabilities. The MCP tools come back into play for the next step.&lt;/p&gt;

&lt;p&gt;One thing I've learned from running these flows: &lt;strong&gt;don't ask Claude Code to wait for the email in the same prompt as the registration.&lt;/strong&gt; Keep registration and email-waiting as separate steps. It's easier to debug when one fails, and it avoids the agent making assumptions about delivery timing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Wait for the Verification Email
&lt;/h2&gt;

&lt;p&gt;This is where the flow either works end-to-end or breaks down in a standard setup. With the MCP server, it's a single blocking tool call:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wait_for_verification_email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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="err"&gt;Output:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"received"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify your account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"from_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noreply@app.example.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;"received_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-06T10:01:34Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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 tool polls the inbox every 3 seconds until a message arrives or the timeout is reached. From Claude Code's perspective this is opaque — it makes one tool call and gets back a result. No retry loop, no repeated tool invocations burning your context budget.&lt;/p&gt;

&lt;p&gt;If the timeout fires before anything arrives, you get:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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;I've found 90 seconds covers the vast majority of transactional email delivery in practice. For services with aggressive rate limiting or slow SMTP relay, you can push it to 120 or 180. For CI pipelines where speed matters more, &lt;a href="https://uncorreotemporal.com/en/blog/testing-email-flows-ci-cd" rel="noopener noreferrer"&gt;60 seconds is usually sufficient&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The response includes &lt;code&gt;message_id&lt;/code&gt; and &lt;code&gt;from_address&lt;/code&gt; but not the email body — that's intentional. Body retrieval is a separate concern handled by the extraction tools in the next step, which can work from &lt;code&gt;message_id&lt;/code&gt; directly without an additional &lt;code&gt;get_latest_email&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;You can also filter by sender or subject if you're dealing with services that send multiple emails on signup:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wait_for_verification_email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_contains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"verify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"from_contains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noreply@app.example.com"&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;h2&gt;
  
  
  Step 5 — Extract the OTP with Regex
&lt;/h2&gt;

&lt;p&gt;Once &lt;code&gt;wait_for_verification_email&lt;/code&gt; returns &lt;code&gt;"status": "received"&lt;/code&gt;, you have a &lt;code&gt;message_id&lt;/code&gt;. Pass it directly to &lt;code&gt;extract_otp_code&lt;/code&gt;:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract_otp_code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.com"&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="err"&gt;Output:&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;"otp_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"847291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"candidates"&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="s2"&gt;"847291"&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 tool fetches the message body internally, runs a regex sweep for numeric sequences in the &lt;code&gt;otp_length_min&lt;/code&gt;–&lt;code&gt;otp_length_max&lt;/code&gt; range (default 4–8 digits), and returns the most likely match as &lt;code&gt;otp_code&lt;/code&gt;. If there are multiple numeric sequences, they all appear in &lt;code&gt;candidates&lt;/code&gt; — useful when the email body contains things like reference numbers or ZIP codes that could false-positive.&lt;/p&gt;

&lt;p&gt;You can narrow the range explicitly:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract_otp_code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"otp_length_min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"otp_length_max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&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;h3&gt;
  
  
  Doing it yourself in Python
&lt;/h3&gt;

&lt;p&gt;If you're building a script rather than using Claude Code interactively, here's the extraction logic without any SDK dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_otp&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;rf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\b\d{{&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;}}\b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_otp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email_body_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# -&amp;gt; "847291"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common OTP patterns you'll encounter in the wild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;6-digit numeric&lt;/strong&gt; (most common, e.g. Twilio, Auth0, most SaaS): &lt;code&gt;\b\d{6}\b&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8-digit numeric&lt;/strong&gt; (banking, government): &lt;code&gt;\b\d{8}\b&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alphanumeric codes&lt;/strong&gt; (e.g. &lt;code&gt;B4K9X2TZ&lt;/code&gt;): &lt;code&gt;\b[A-Z0-9]{8}\b&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Magic links&lt;/strong&gt;: handled separately by &lt;code&gt;extract_verification_link&lt;/code&gt; (see the next section)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6 — Complete the Registration
&lt;/h2&gt;

&lt;p&gt;You have the OTP. Now Claude Code submits it. The prompt is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The OTP extracted from the verification email is 847291. Submit it in the 
verification form on app.example.com. Once submitted, confirm that the 
account dashboard is visible and report whether registration succeeded.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code navigates back to the verification form, enters the code, and submits. If the service uses a magic link instead of a code, the flow is identical — you just pass the URL from &lt;code&gt;extract_verification_link&lt;/code&gt; instead of a 6-digit code.&lt;/p&gt;

&lt;p&gt;At this point the agent loop is complete. No human touched it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It All Together — A Complete Agent Loop Prompt
&lt;/h2&gt;

&lt;p&gt;Here's the prompt I use for end-to-end autonomous registration. Copy it, swap &lt;code&gt;app.example.com&lt;/code&gt; for your actual target, and adjust the password and name as needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an autonomous registration agent. Complete the following workflow 
in order, stopping if any step fails:

1. Call create_signup_inbox with service_name "app.example.com" and store 
   the returned email address.

2. Register a free account on app.example.com using:
   - Email: the address from step 1
   - Name: Alex Developer
   - Password: TempP@ss2026!
   Fill all required fields. Submit the registration form.

3. Call wait_for_verification_email with the inbox_id from step 1 and 
   timeout_seconds 90. Wait for status "received".

4. Call extract_otp_code with the message_id and inbox_id from the 
   previous steps.

5. Submit the extracted OTP code in the verification form on app.example.com.

6. Confirm the account dashboard is accessible, then report:
   - Email address used
   - OTP extracted
   - Whether registration succeeded (yes/no)
   - Any unexpected behavior during the flow

If any step returns an error or unexpected result, report exactly what 
happened and stop. Do not retry automatically.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why the prompt is structured this way
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Numbered steps enforce sequence.&lt;/strong&gt; Claude Code can parallelize tool calls when it reasons that two actions are independent. An explicit numbered list signals that order matters and each step depends on the previous one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit tool names in the prompt.&lt;/strong&gt; Saying "call &lt;code&gt;create_signup_inbox&lt;/code&gt;" rather than "create an inbox" removes ambiguity about which MCP tool to use. It also makes the agent's reasoning trace easier to follow when you're debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Do not retry automatically."&lt;/strong&gt; This is the most important line. When a step fails — email never arrives, OTP is wrong, form submission errors — you want the agent to stop and report, not loop into a retry spiral that burns API quota and potentially triggers rate limits on the target service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured report at the end.&lt;/strong&gt; The bulleted output at step 6 gives you something machine-parseable if you're chaining this into a larger pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling Failure Cases
&lt;/h2&gt;

&lt;p&gt;Real-world flows break in predictable ways. Here's how to handle them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Email never arrives
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;wait_for_verification_email&lt;/code&gt; returns &lt;code&gt;{"status": "timeout"}&lt;/code&gt; after the configured seconds. Common causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The service's SMTP relay is slow (some providers queue outbound email for 30–60 seconds under load)&lt;/li&gt;
&lt;li&gt;The email was delivered but classified as spam and filtered before it reached the inbox&lt;/li&gt;
&lt;li&gt;The registration form rejected the address silently and never sent the email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mitigation: increase &lt;code&gt;timeout_seconds&lt;/code&gt; to 120 or 180 for slow senders. If you're seeing consistent timeouts, call &lt;code&gt;get_latest_email&lt;/code&gt; after the timeout to check whether anything arrived at all — sometimes the email arrives just after the timeout window closes.&lt;/p&gt;

&lt;h3&gt;
  
  
  OTP already expired by the time you submit it
&lt;/h3&gt;

&lt;p&gt;Most OTPs expire in 10 minutes. The entire flow — create inbox, register, wait for email, extract OTP, submit — should complete in well under 60 seconds in practice. If your agent is slow (large context, slow browser automation), request a new OTP from the service before submitting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service blocks the email domain
&lt;/h3&gt;

&lt;p&gt;Some services — Google, Apple, Stripe, most banking platforms — maintain blocklists of known disposable email providers and reject addresses from those domains at signup. If you hit this, you'll know immediately: the registration form will return an error like "Please use a valid email address."&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://uncorreotemporal.com/en/pricing/" rel="noopener noreferrer"&gt;UCT dedicated plans&lt;/a&gt; include support for custom domains — you can receive email at &lt;code&gt;agent@yourdomain.com&lt;/code&gt; instead of the default &lt;code&gt;@mail.uncorreotemporal.com&lt;/code&gt;. That bypasses most domain-level blocklists.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond OTPs — Magic Links and Multi-Step Flows
&lt;/h2&gt;

&lt;p&gt;Not every service sends a 6-digit code. Plenty of them send a magic link — a one-time URL that logs you in or confirms your email when you visit it. The MCP server handles this with &lt;code&gt;extract_verification_link&lt;/code&gt;:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract_verification_link&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"preferred_domains"&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="s2"&gt;"app.example.com"&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="err"&gt;Output:&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;"verification_link"&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://app.example.com/verify?token=eyJhbGci..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"candidates"&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="s2"&gt;"https://app.example.com/verify?token=eyJhbGci..."&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://app.example.com/unsubscribe?email=..."&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;&lt;code&gt;preferred_domains&lt;/code&gt; nudges the extraction toward links from the service's own domain, filtering out unsubscribe links, tracking pixels, and other noise that appears in most HTML email.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-step flows
&lt;/h3&gt;

&lt;p&gt;The same tool chain composes naturally for flows that span multiple emails. A common pattern in developer-facing SaaS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register -&amp;gt; receive email confirmation link -&amp;gt; click it -&amp;gt; account activated&lt;/li&gt;
&lt;li&gt;Account activated -&amp;gt; receive welcome email with API key -&amp;gt; extract API key&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need to restart the agent or re-create the inbox between steps. The inbox stays live for its full TTL, and &lt;code&gt;wait_for_verification_email&lt;/code&gt; can be called multiple times against the same &lt;code&gt;inbox_id&lt;/code&gt;. Chain two &lt;code&gt;wait_for_verification_email&lt;/code&gt; + &lt;code&gt;extract_*&lt;/code&gt; call pairs and you have the whole flow in one agent session.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;complete_signup_flow&lt;/code&gt; shortcut
&lt;/h3&gt;

&lt;p&gt;If your flow is straightforward — register, wait for one verification email, extract the link or OTP — there's a single tool that wraps all of it:&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="err"&gt;Tool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;complete_signup_flow&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Input:&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;"service_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.example.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;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&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="err"&gt;Output:&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;"inbox_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mx_abc123@mail.uncorreotemporal.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;"otp_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"847291"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verification_link"&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://app.example.com/verify?token=..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_9x2p1q"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"received"&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;&lt;code&gt;complete_signup_flow&lt;/code&gt; creates the inbox, waits for the email, and runs both OTP and link extraction in sequence. You get everything back in one response. For simple flows this is the cleanest approach; for multi-step or filtered flows, calling the individual tools gives you more control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The fastest path from zero to a working agent loop is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://uncorreotemporal.com/en/pricing/" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt; — no credit card, API key in the dashboard immediately&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;claude mcp add uncorreotemporal -e UCT_API_KEY=uct_your_key_here -- uvx uncorreotemporal-mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open a Claude Code session, type &lt;code&gt;/tools&lt;/code&gt;, confirm the six tools are listed&lt;/li&gt;
&lt;li&gt;Paste the agent loop prompt from the "Putting It All Together" section, point it at a real service, and watch it run&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The free tier gives you 100 inboxes per month, which is enough to wire up and test a full registration flow. If you're running this in CI — automated integration tests, synthetic monitoring, agent-driven QA — the full &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;MCP tool reference&lt;/a&gt; covers filtering options, TTL configuration, and the &lt;code&gt;get_latest_email&lt;/code&gt; tool for cases where you need the raw message body.&lt;/p&gt;

&lt;p&gt;The email verification wall is a solved problem. Your agent loop doesn't have to stop there.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>mcp</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why Every AI Agent Stack Needs an Email Verification Layer</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Thu, 30 Apr 2026 19:10:36 +0000</pubDate>
      <link>https://dev.to/francofuji/why-every-ai-agent-stack-needs-an-email-verification-layer-1pai</link>
      <guid>https://dev.to/francofuji/why-every-ai-agent-stack-needs-an-email-verification-layer-1pai</guid>
      <description>&lt;p&gt;Your agent just submitted a registration form. The third-party service responded with a 200 OK and a message: "Check your inbox for a verification link."&lt;/p&gt;

&lt;p&gt;The agent cannot check an inbox. It doesn't have one. It used your dev email address — the one that belongs to a human who is in a different time zone and probably not watching their email right now. The agent is stuck at the wall. The pipeline is stalled. And the frustrating part is that every other step worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent email verification&lt;/strong&gt; is one of the most common hard stops in agentic pipelines. Agents can browse the web, call APIs, fill forms, parse HTML, and chain together dozens of steps — but the moment a service sends a verification email, most of them hit a dead end. This article explains why that happens, what the actual requirements are for email infrastructure that works with agents, and how to wire up a purpose-built MCP email server so it never happens again.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed — From Chatbots to Agents That Actually Do Things
&lt;/h2&gt;

&lt;p&gt;A chatbot suggests. An agent executes.&lt;/p&gt;

&lt;p&gt;The distinction matters here. Chatbots operate in a request-response loop with a human at one end. Agents operate in a perceive-reason-act loop that runs autonomously — they navigate UI flows, call external APIs, handle state across steps, and complete tasks without a human approving each action.&lt;/p&gt;

&lt;p&gt;The infrastructure that made this practical at scale is &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;, an open standard released by Anthropic and now the de-facto interoperability layer for AI agent tooling. MCP defines a canonical interface for exposing capabilities to agents: a server registers typed tools, and any MCP-compatible runtime — Claude, Copilot, AutoGen, LangGraph, n8n — can discover and call those tools without custom glue code per integration. By 2025-2026, Google, Microsoft, and Amazon had all adopted MCP as a core part of their agent platform strategies.&lt;/p&gt;

&lt;p&gt;The practical consequence: an agent without the right tools is just a chatbot with extra steps. Tools are what give agents agency. A browser tool lets the agent navigate. A code execution tool lets it write and run scripts. A search tool lets it query the web. And an email tool — specifically, a purpose-built one that creates real inboxes and reads from them programmatically — is what lets it complete the signup flows, verification steps, and notification-driven workflows that are gated behind email.&lt;/p&gt;

&lt;p&gt;Without it, every email-gated flow becomes a manual intervention point. That is the failure mode most teams discover late, usually at 2am when a pipeline that worked in testing falls apart against a real service.&lt;/p&gt;




&lt;h2&gt;
  
  
  Email Verification Is the Silent Killer of Agentic Flows
&lt;/h2&gt;

&lt;p&gt;Most people underestimate how often email verification appears in real-world automation. Account registration, API key delivery, purchase confirmations, approval workflows, password resets — a significant fraction of services require a working inbox before they grant access to anything.&lt;/p&gt;

&lt;p&gt;The problem has four distinct failure modes when you try to handle email without proper infrastructure:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The agent must own the inbox before it signs up.&lt;/strong&gt; You cannot verify an address you do not control. The correct sequence is: create inbox -&amp;gt; get address -&amp;gt; submit form -&amp;gt; wait for email -&amp;gt; extract token. Most agent implementations skip the first step and use the developer's real email address, which collapses the rest of the flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Using a real email doesn't scale.&lt;/strong&gt; One inbox shared across all agent runs causes immediate problems: parallel runs contaminate each other's messages, OTP codes from different registrations get mixed up, and your dev account accumulates thousands of verification messages. That is also a privacy and compliance problem — real user email addresses attached to test accounts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Public disposable email services are blocked.&lt;/strong&gt; Mailinator, YOPmail, and similar consumer tools are blocked by most production services. They also have no API suitable for agents — no authentication, no reliable delivery, no polling interface that code can depend on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. IMAP polling is a trap.&lt;/strong&gt; The approach that seems obvious — connect to Gmail via IMAP, poll for new messages — breaks in an agent context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# naive approach -- don't do this
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;imaplib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wait_for_otp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imaplib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IMAP4_SSL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imap.gmail.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# your real credentials in agent context
&lt;/span&gt;    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNSEEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parse_otp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# brittle regex against raw MIME
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# blocking the agent loop for 5 seconds per cycle
&lt;/span&gt;    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No OTP received&lt;/span&gt;&lt;span class="sh"&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's wrong here: real credentials living in the agent's execution context, no inbox isolation between parallel runs, polling blocks the agent loop, IMAP connections are stateful and flaky, and regex against raw MIME breaks on any whitespace change in the email template.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Purpose-Built Agent Email Layer Looks Like
&lt;/h2&gt;

&lt;p&gt;The requirements follow directly from how agents work. Missing any one of them creates a failure mode at scale.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Programmatic inbox creation&lt;/strong&gt;: one API call returns a live email address — no UI, no human step, no waiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped TTL&lt;/strong&gt;: the inbox expires automatically. Thirty minutes is enough for a signup flow. No garbage collection required from the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No polling loops in agent code&lt;/strong&gt;: the agent calls &lt;code&gt;wait_for_verification_email&lt;/code&gt; with a timeout and gets a result when the email arrives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native extraction&lt;/strong&gt;: the tool returns &lt;code&gt;otp_code&lt;/code&gt; or &lt;code&gt;verification_link&lt;/code&gt; as a structured field. The agent does not write regex or parse MIME.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inbox isolation&lt;/strong&gt;: each agent run gets its own inbox. Parallel pipelines do not share state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP-native&lt;/strong&gt;: the agent uses email as a tool primitive, not a side channel requiring custom adapter code per framework.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The MCP Email Server in Practice
&lt;/h2&gt;

&lt;p&gt;There are two integration paths. You can use the REST API directly from any agent that can make HTTP calls, or the MCP server if your agent runtime supports Model Context Protocol.&lt;/p&gt;

&lt;h3&gt;
  
  
  Approach A — REST API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: Create a temporary inbox
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://uncorreotemporal.com/api/v1/mailboxes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer uct_xxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttl_minutes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="n"&gt;email_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# e.g. "mango-panda-42@uncorreotemporal.com"
# inbox also contains: expires_at
&lt;/span&gt;
&lt;span class="c1"&gt;# Step 2: Use this address to sign up somewhere
# ... your agent submits the registration form with email_address ...
&lt;/span&gt;
&lt;span class="c1"&gt;# Step 3: Poll for the verification email
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# up to 90 seconds
&lt;/span&gt;    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://uncorreotemporal.com/api/v1/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email_address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer uct_xxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&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;# Each message has: id, from_address, subject, received_at, is_read
&lt;/span&gt;    &lt;span class="n"&gt;unread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is_read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="c1"&gt;# Step 4: Read the full message
&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://uncorreotemporal.com/api/v1/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email_address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;unread&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer uct_xxxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&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;# msg contains: body_text, body_html, from_address, subject, received_at
&lt;/span&gt;
&lt;span class="n"&gt;verification_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Approach B — MCP Tools
&lt;/h3&gt;

&lt;p&gt;Add the server to your &lt;code&gt;claude_desktop_config.json&lt;/code&gt;:&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;"uncorreotemporal"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"uncorreotemporal-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;"env"&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;"UCT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uct_your_key_here"&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;With the MCP server registered, Claude and other MCP-compatible agents complete the entire verification flow using native tool calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Tool 1: create_signup_inbox
&lt;/span&gt;&lt;span class="n"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_signup_inbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl_minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Returns: {"inbox_id": "mango-panda-42@uncorreotemporal.com",
#           "email": "mango-panda-42@uncorreotemporal.com",
#           "expires_at": "2026-04-30T15:30:00Z"}
&lt;/span&gt;
&lt;span class="c1"&gt;# ... agent submits signup form using inbox["email"] ...
&lt;/span&gt;
&lt;span class="c1"&gt;# Tool 2: wait_for_verification_email
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wait_for_verification_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;timeout_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subject_contains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Returns: {"status": "received", "message_id": "3f8a1c2b-...",
#           "subject": "Please verify your account"}
&lt;/span&gt;
&lt;span class="c1"&gt;# Tool 3a: extract_verification_link
&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_verification_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Returns: {"verification_link": "https://github.com/confirm?token=abc123"}
&lt;/span&gt;
&lt;span class="c1"&gt;# Tool 3b: extract_otp_code
&lt;/span&gt;&lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_otp_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Returns: {"otp_code": "847291", "candidates": ["847291"]}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why MCP is cleaner: the agent gets tools as native primitives with typed schemas. No credential management in agent context. No adapter code per framework. There is also a &lt;code&gt;complete_signup_flow&lt;/code&gt; tool that wraps the entire sequence in a single call.&lt;/p&gt;

&lt;p&gt;Full setup docs: &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;https://uncorreotemporal.com/en/docs/mcp/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond One-Off Testing — Where This Scales
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Parallel agent workloads.&lt;/strong&gt; A pipeline running 50 sub-agents simultaneously needs 50 isolated inboxes, all created programmatically, all expiring on schedule. The Pro plan supports 20 concurrent mailboxes with 2,000 requests/day; the Team plan handles 100 mailboxes at 10,000 req/day. Sized for production agent workloads: &lt;a href="https://uncorreotemporal.com/en/pricing/" rel="noopener noreferrer"&gt;https://uncorreotemporal.com/en/pricing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy by design.&lt;/strong&gt; Agents acting on behalf of users should never touch real user email addresses. Ephemeral inboxes make this a structural guarantee — the address expires, the data disappears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance.&lt;/strong&gt; In regulated industries, test data must not include real PII. An inbox at &lt;code&gt;mango-panda-42@uncorreotemporal.com&lt;/code&gt; has no connection to any real person. It expires on schedule. It leaves no trail.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The verification email that stops your agent is not a hard problem — it is a missing infrastructure problem. Add a purpose-built email layer that creates isolated inboxes on demand, waits for delivery without polling loops, and extracts tokens as native tool outputs.&lt;/p&gt;

&lt;p&gt;To get started: MCP setup docs at &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;https://uncorreotemporal.com/en/docs/mcp/&lt;/a&gt; — or try the free plan (1 mailbox, 50 req/day) at &lt;a href="https://uncorreotemporal.com/en/pricing/" rel="noopener noreferrer"&gt;https://uncorreotemporal.com/en/pricing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://uncorreotemporal.com/en/blog/ai-agent-email-verification-mcp-server/" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>python</category>
      <category>automation</category>
    </item>
    <item>
      <title>Agentic Web Email Infrastructure</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Tue, 28 Apr 2026 20:09:43 +0000</pubDate>
      <link>https://dev.to/francofuji/agentic-web-email-infrastructure-12h9</link>
      <guid>https://dev.to/francofuji/agentic-web-email-infrastructure-12h9</guid>
      <description>&lt;p&gt;Your agent is three steps away from completing a registration. It filled out the form, submitted it, and got a 200 OK back. The service responds: "Check your inbox for a verification link."&lt;/p&gt;

&lt;p&gt;And there the agent stops.&lt;/p&gt;

&lt;p&gt;There is no inbox to check. The email address it used was your dev account — the one you use for everything — and that account belongs to a human who is probably asleep. There is no tool the agent can call that returns "the link from the email that just arrived." It has done everything right, and it is stuck.&lt;/p&gt;

&lt;p&gt;This is the agentic web email infrastructure problem. Agents can browse pages, call APIs, write code, and run shell commands. But most of them cannot receive email — and an astonishing fraction of real services require email verification before granting any access at all. Until your agent can create an inbox, read from it programmatically, and extract tokens from the messages that arrive, it will keep failing at this wall.&lt;/p&gt;

&lt;p&gt;The rest of this article explains why this is architecturally harder than it looks, and what a proper solution requires.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Agentic Web Actually Means
&lt;/h2&gt;

&lt;p&gt;The shift happening right now is not about smarter chatbots. It is about systems that follow a complete perceive → reason → act loop without a human approving each step.&lt;/p&gt;

&lt;p&gt;In 2026, this is production behavior. GitHub Copilot opens pull requests, runs tests, iterates on reviewer feedback, and merges — end to end without a developer in the loop for each cycle. Google Search can schedule appointments by calling restaurant booking APIs directly. Cloudflare and GoDaddy ship integrations where agents register domains, provision DNS records, and deploy edge workers autonomously. These are not demos. These are workflows that previously required a person sitting at a computer.&lt;/p&gt;

&lt;p&gt;The interoperability layer making all of this possible is &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;, an open standard released by Anthropic and now adopted across the industry. MCP defines how agents discover and invoke external tools: a schema-annotated registry of capabilities that any MCP-compatible runtime — Claude, Cursor, AutoGen, LangGraph, n8n — can use without writing custom adapter code for each integration. An agent registers a server, gets a list of typed tools back, and can call them the same way it calls any other capability.&lt;/p&gt;

&lt;p&gt;As agents take on more real-world tasks — account provisioning, API access registration, subscription management, collecting data from services that require login — they increasingly hit the same structural blocker. Email-gated flows are not a fringe edge case. They sit at the entrance to most services on the internet, and the agentic web email infrastructure needed to get past them is almost always missing from the stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Email Verification Wall
&lt;/h2&gt;

&lt;p&gt;Email verification is the most common blocker for autonomous agents because the standard flow makes three assumptions that break the moment an agent is involved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The inbox exists before the signup.&lt;/strong&gt; A human has an email address before they visit the registration page. An agent must create the inbox first, get the address back, then use that address in the form. The sequence matters: you cannot verify an address you do not already control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A human is monitoring the inbox.&lt;/strong&gt; OTP codes expire in minutes. Verification links sometimes expire in hours. The standard UX — sign up, switch to email, click the link — assumes continuous human availability. An agent needs a programmatic flow: create inbox, submit form, poll for the message, extract the token, complete the flow. No human hand-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The email address is stable and personal.&lt;/strong&gt; Using your own dev email for agent-driven signups pollutes your inbox with thousands of verification messages from test runs. It also creates a real privacy risk: your email is now attached to every service account the agent created, forever.&lt;/p&gt;

&lt;p&gt;The naive workaround looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What most teams try first — and why it fails
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-dev-account@gmail.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# shared across all runs, pollutes inbox
&lt;/span&gt;
&lt;span class="c1"&gt;# Submit the signup form
&lt;/span&gt;&lt;span class="n"&gt;requests&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://someservice.com/register&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EMAIL&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Wait and hope
&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# no feedback, brittle in CI/CD, timing is a guess
&lt;/span&gt;
&lt;span class="c1"&gt;# No programmatic access to Gmail without a full OAuth setup
# The agent is stuck. The only option is asking a human to check.
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please verify manually&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# defeats the purpose entirely
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This fails in CI because there is no API to read the inbox. It fails across parallel runs because multiple agents share the same address. It fails against services with short OTP TTLs. And it creates a compliance problem every time a real user email ends up attached to a test account.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Proper Email Infrastructure Looks Like for an Agent
&lt;/h2&gt;

&lt;p&gt;A production-grade AI agent email solution needs to satisfy six requirements. Missing any one of them creates a failure mode at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Programmatic inbox creation.&lt;/strong&gt; The agent must be able to call an API or MCP tool to create a fresh inbox and get back a live email address. Not a UI, not a manual step — a single function call that returns an address ready to receive SMTP traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Configurable TTL.&lt;/strong&gt; Inboxes should expire automatically. Thirty minutes for a signup flow, twenty-four hours for a batch pipeline. The agent declares intent; the infrastructure enforces cleanup. No garbage collection required from the agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Sub-200ms delivery notification.&lt;/strong&gt; An agent polling with a five-second sleep introduces up to five seconds of latency per email retrieval. Real pub/sub delivery — or at minimum a polling endpoint with fresh-data guarantees — is what keeps pipelines fast enough to be useful in CI/CD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. OTP and link extraction as native primitives.&lt;/strong&gt; The agent should not be writing regex against raw email bodies. OTP extraction and verification link extraction should be first-class tool calls that return structured fields — &lt;code&gt;otp_code&lt;/code&gt;, &lt;code&gt;verification_link&lt;/code&gt; — not raw strings the agent has to parse itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. No shared inboxes.&lt;/strong&gt; Each agent, each run, each task should own its inbox exclusively. A shared inbox leaks tokens between runs, creates race conditions in parallel pipelines, and mixes data between agents operating at different trust levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. API key scoping.&lt;/strong&gt; Per-key quotas and team plan rate limits prevent a runaway agent from exhausting the inbox budget for the entire organization. The key belongs to the agent system, not to an individual developer account.&lt;/p&gt;

&lt;p&gt;These requirements are what separate infrastructure designed for autonomous agents from a consumer-grade disposable email service that someone hacked into a pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  UnCorreoTemporal in Practice — A Real Agent Flow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;UnCorreoTemporal&lt;/a&gt; provides an MCP server built specifically for autonomous agent signup flows. Five tools cover the complete flow. Here is a working Python example using the real tool names and field names from the codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StdioServerParameters&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.client.stdio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stdio_client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;server_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StdioServerParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uvx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uncorreotemporal-mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UCT_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uct_your_api_key_here&lt;/span&gt;&lt;span class="sh"&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;complete_signup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;stdio_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;# Step 1: Create a temporary inbox (TTL: 30 minutes)
&lt;/span&gt;            &lt;span class="n"&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="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_signup_inbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example-service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ttl_minutes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="n"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="n"&gt;email&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# e.g. "mango-panda-42@uncorreotemporal.com"
&lt;/span&gt;            &lt;span class="n"&gt;inbox_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# same value; used to poll for messages
&lt;/span&gt;
            &lt;span class="c1"&gt;# Step 2: Submit the signup form with the temporary address
&lt;/span&gt;            &lt;span class="n"&gt;requests&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;service_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/register&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="c1"&gt;# Step 3: Wait for the verification email (polls automatically)
&lt;/span&gt;            &lt;span class="n"&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="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wait_for_verification_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timeout_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject_contains&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="n"&gt;waited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;waited&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;received&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Verification email did not arrive within timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;message_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;waited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="c1"&gt;# Step 4a: Extract OTP code (if the service uses numeric codes)
&lt;/span&gt;            &lt;span class="n"&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="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extract_otp_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message_id&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="n"&gt;otp_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;otp_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otp_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# e.g. "847291"
&lt;/span&gt;
            &lt;span class="c1"&gt;# Step 4b: Or extract verification link (if the service uses click-to-verify)
&lt;/span&gt;            &lt;span class="n"&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="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extract_verification_link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inbox_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message_id&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="n"&gt;link_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;link_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verification_link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Step 5: Complete the flow
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;requests&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;service_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;complete_signup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://someservice.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP server handles all the infrastructure: creating the mailbox via &lt;code&gt;POST https://uncorreotemporal.com/api/v1/mailboxes&lt;/code&gt;, polling for delivery, and extracting structured tokens from the raw email body. The agent just calls tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters Beyond Testing
&lt;/h2&gt;

&lt;p&gt;The most obvious use case is CI/CD: integration tests that need a fresh inbox per run, per test case, with zero shared state. But the same infrastructure applies to production agent architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-agent pipelines.&lt;/strong&gt; A research agent, a scheduling agent, and a delivery agent in the same pipeline may each need separate accounts on different services. Each gets its own inbox, its own identity. No cross-contamination between tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy-preserving agent architectures.&lt;/strong&gt; An agent acting on behalf of a user should never expose that user's real email address to third-party services. Temporary inboxes decouple the user's identity from the agent's operational identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Regulatory compliance.&lt;/strong&gt; Agents that register accounts using real user emails attach PII to every service they touch. Using ephemeral inboxes keeps PII out of third-party data stores — directly relevant to GDPR, CCPA, and any compliance framework that tracks where user data flows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale.&lt;/strong&gt; One hundred parallel agents each completing a signup flow simultaneously need one hundred isolated inboxes, created and expired programmatically. There is no manual coordination approach that works at that scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The agentic web email infrastructure problem is not going away — it is growing proportionally with agent capability. The five MCP tools covered here — &lt;code&gt;create_signup_inbox&lt;/code&gt;, &lt;code&gt;wait_for_verification_email&lt;/code&gt;, &lt;code&gt;get_latest_email&lt;/code&gt;, &lt;code&gt;extract_otp_code&lt;/code&gt;, &lt;code&gt;extract_verification_link&lt;/code&gt; — cover the majority of real agent email workflows without requiring the agent to manage SMTP, parse raw message formats, or write custom extraction logic.&lt;/p&gt;

&lt;p&gt;Get the MCP server running in minutes at &lt;a href="https://uncorreotemporal.com/en/docs/mcp/" rel="noopener noreferrer"&gt;uncorreotemporal.com/en/docs/mcp/&lt;/a&gt;, or start with a free plan at &lt;a href="https://uncorreotemporal.com/en/pricing/" rel="noopener noreferrer"&gt;uncorreotemporal.com/en/pricing/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://uncorreotemporal.com/blog/agentic-web-autonomous-agents-email-infrastructure" rel="noopener noreferrer"&gt;uncorreotemporal.com/blog/agentic-web-autonomous-agents-email-infrastructure&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Why AI Agents in n8n Fail on Real-World Workflows (and How to Fix It)</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Sun, 19 Apr 2026 13:38:04 +0000</pubDate>
      <link>https://dev.to/francofuji/why-ai-agents-in-n8n-fail-on-real-world-workflows-and-how-to-fix-it-3ca</link>
      <guid>https://dev.to/francofuji/why-ai-agents-in-n8n-fail-on-real-world-workflows-and-how-to-fix-it-3ca</guid>
      <description>&lt;h2&gt;
  
  
  The Gap Between the Demo and the Real World
&lt;/h2&gt;

&lt;p&gt;The demo always works. You wire up an LLM node in n8n, chain a few HTTP requests, and the agent navigates through a multi-step workflow without a hitch. Then you point it at something real — a signup flow, a SaaS trial, a service that requires email verification — and the workflow halts.&lt;/p&gt;

&lt;p&gt;Not because the reasoning was wrong. The agent chose the right action. It decided to create an account. It selected the right form fields. It even generated a valid request.&lt;/p&gt;

&lt;p&gt;It failed because the service sent a verification email and the agent had no way to receive it.&lt;/p&gt;

&lt;p&gt;This is the pattern that breaks most real-world n8n agent pipelines: they are designed for deterministic, synchronous systems, but the real web is asynchronous and email-dependent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Promise of n8n + AI Agents
&lt;/h2&gt;

&lt;p&gt;n8n is a genuinely strong orchestration layer. It handles node chaining, branching, retries, credential management, and webhook ingestion cleanly. When you connect it to an LLM via the AI Agent node, you get a workflow that can reason about inputs, call external APIs, and decide what to do next.&lt;/p&gt;

&lt;p&gt;This combination works well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scraping structured data and transforming it&lt;/li&gt;
&lt;li&gt;Calling APIs with conditional logic&lt;/li&gt;
&lt;li&gt;Generating content from templates or user inputs&lt;/li&gt;
&lt;li&gt;Classifying and routing incoming data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM handles ambiguity. n8n handles orchestration. For many workflows, this is enough.&lt;/p&gt;

&lt;p&gt;The problem surfaces when the workflow leaves the API-accessible world and enters real-world system interactions — specifically, anything that routes through email.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Things Break
&lt;/h2&gt;

&lt;p&gt;Email verification is the most common hard wall. Consider any of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Account registration&lt;/strong&gt; on a third-party service that requires email confirmation before the account is active&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTP-based login&lt;/strong&gt; where a time-limited code arrives in an inbox&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Magic link authentication&lt;/strong&gt;, where the session token is delivered via email&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trial onboarding flows&lt;/strong&gt; that gate API key generation behind an email confirmation step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not edge cases. They are standard authentication patterns across most of the web. An agent that cannot handle them cannot operate autonomously in real environments.&lt;/p&gt;

&lt;p&gt;The deeper problem is structural. Email delivery is asynchronous. The time between a signup request and the arrival of a verification email is non-deterministic — it could be two seconds or forty-five. There is no webhook you can register. There is no polling endpoint you can call against the sender. The email arrives at an inbox, and something needs to be watching that inbox.&lt;/p&gt;

&lt;p&gt;In a standard n8n workflow, there is nothing watching.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Concrete Example: Agent Gets Stuck
&lt;/h2&gt;

&lt;p&gt;Imagine an n8n workflow where an AI agent is tasked with registering a trial account on a data API service to test a new integration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[AI Agent Node]
  -&amp;gt; decides: "I need to create a test account on api.someservice.com"
  -&amp;gt; calls: POST /api/v1/signup  { email: ???, name: "Test Agent" }
  -&amp;gt; receives: { status: "pending", message: "Check your email to verify" }
  -&amp;gt; next step: ???
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent needs an email address to register. If you hardcode your personal inbox, the verification email lands in a human mailbox — someone has to click the link manually. The workflow is no longer autonomous.&lt;/p&gt;

&lt;p&gt;The typical workarounds developers try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Option 1: Use a static dummy address
# -&amp;gt; verification never arrives, account stays unconfirmed
&lt;/span&gt;
&lt;span class="c1"&gt;# Option 2: Use a public throwaway service (Guerrilla Mail, temp-mail.org)
# -&amp;gt; no API, browser-only, no way to read messages programmatically
&lt;/span&gt;
&lt;span class="c1"&gt;# Option 3: Use Gmail + polling via OAuth
# -&amp;gt; requires OAuth setup, scopes, token refresh, high latency,
#   and you're mixing test email with real email
&lt;/span&gt;
&lt;span class="c1"&gt;# Option 4: Give up and inject a pre-verified account
# -&amp;gt; the workflow is no longer testing what it claims to test
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these are real solutions. They either break automation or introduce manual steps that defeat the purpose of the agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Root Cause Analysis
&lt;/h2&gt;

&lt;p&gt;The failure has four technical causes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Async delivery with no callback.&lt;/strong&gt; Email follows a push model — the sender's SMTP server delivers to a recipient's MX record. There is no mechanism for the receiving side to signal "I got it" back to the sender in a structured way. The gap between send and receive is real and variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Polling requires infrastructure.&lt;/strong&gt; To poll an inbox programmatically, you need a mailbox that exposes a REST interface. Standard IMAP is available for personal accounts, but requires OAuth flows, credential management, and careful scope handling. It is not agent-friendly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Unstructured content.&lt;/strong&gt; Even when you can receive an email, extracting the OTP or verification link requires parsing free-form text or HTML. Services do not follow a standard. One sends &lt;code&gt;Your code is: 482910&lt;/code&gt;. Another buries the link in three layers of tracking redirects inside &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags. You need extraction logic per-sender or a general-purpose parser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. No ephemeral inbox primitive.&lt;/strong&gt; Agents need disposable, programmatically-controlled inboxes that can be created on demand, pointed at a specific signup flow, read via API, and discarded. No standard infrastructure provides this at the level of a simple REST call.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix: Programmable Inboxes With Structured Extraction
&lt;/h2&gt;

&lt;p&gt;The conceptual fix has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create an inbox on demand&lt;/strong&gt; — an address that is real (routes actual SMTP traffic) and accessible via REST API without OAuth or manual setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wait for email with a timeout&lt;/strong&gt; — poll the inbox until a message arrives or a deadline is reached&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract structured data&lt;/strong&gt; — pull the OTP or verification link from the body in a way the next workflow node can consume&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This turns an asynchronous, infrastructure-heavy problem into a synchronous, API-driven one. From the perspective of the n8n workflow, email becomes just another HTTP call.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Implementation
&lt;/h2&gt;

&lt;p&gt;Here is how this works end-to-end using the &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt; API, which exposes real SMTP inboxes over REST.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Create an inbox:&lt;/strong&gt;&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://uncorreotemporal.com/api/v1/mailboxes?ttl_minutes=30"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mango-panda-42@uncorreotemporal.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;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-18T23:04:00.000000+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"q7Qq...long_token...1Q"&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;Use &lt;code&gt;address&lt;/code&gt; as the email in your signup request. The &lt;code&gt;session_token&lt;/code&gt; is what authenticates subsequent reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Poll for the verification email:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unquote&lt;/span&gt;

&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://uncorreotemporal.com/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mango-panda-42@uncorreotemporal.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Session-Token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q7Qq...long_token...1Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Poll until a matching message arrives (up to ~150 seconds)
&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&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="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="bp"&gt;None&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="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msg_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Verification email did not arrive within timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3 — Extract the link or OTP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fetch the full message
&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&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="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Extract verification link
&lt;/span&gt;&lt;span class="n"&gt;url_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https?://[^\s\"&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;gt;]+confirm[^\s\"&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;gt;]*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;unquote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Or extract OTP
&lt;/span&gt;&lt;span class="n"&gt;otp_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b\d{4,8}\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;verification_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url_match&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;otp_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;otp_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;otp_match&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In n8n, this maps directly to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;HTTP Request&lt;/strong&gt; node to create the inbox and get &lt;code&gt;address&lt;/code&gt; + &lt;code&gt;session_token&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Loop + Wait&lt;/strong&gt; pattern with an &lt;strong&gt;IF&lt;/strong&gt; node checking for incoming messages&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Code&lt;/strong&gt; node doing the regex extraction&lt;/li&gt;
&lt;li&gt;The next node consuming &lt;code&gt;verification_url&lt;/code&gt; or &lt;code&gt;otp_code&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow stays headless. No human inbox involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem Is Not Reasoning
&lt;/h2&gt;

&lt;p&gt;There is a tendency to attribute agent failures to model quality. The agent "hallucinated," it "misunderstood" the task, it "made a wrong decision."&lt;/p&gt;

&lt;p&gt;In the cases described above, the model is not at fault. It reasoned correctly. It identified the right action. It issued the right API call. What failed was the absence of infrastructure to complete that action.&lt;/p&gt;

&lt;p&gt;AI agents fail on real-world workflows not because of bad reasoning, but because the interfaces they need to interact with — email inboxes, async callbacks, unstructured content — do not expose a clean, synchronous API. The agent cannot be smarter than the tools it has access to.&lt;/p&gt;

&lt;p&gt;Fixing this means treating email as a first-class infrastructure primitive in agent workflows, the same way you treat databases, HTTP APIs, or queues. It requires real inboxes, real SMTP delivery, and a REST interface that agents can call without human intermediaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Most gaps in real-world agent performance trace back to the same root: the world is not fully API-accessible. Email is the most common example, but the pattern applies anywhere a workflow depends on an asynchronous external event that the agent cannot observe directly.&lt;/p&gt;

&lt;p&gt;The good news is that the infrastructure problem is solvable. Once email becomes a programmable, API-accessible layer, a whole class of workflows — account registration, OTP retrieval, verification flows — becomes automatable without manual intervention.&lt;/p&gt;

&lt;p&gt;The more interesting question is: what other asynchronous interfaces are your agents currently hitting a wall on, and is the bottleneck the model or the missing infrastructure?&lt;/p&gt;

&lt;p&gt;If you want to explore programmable email for your agent workflows, the REST API is documented at &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>AI Agents Need Real Email Infrastructure</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Fri, 17 Apr 2026 03:17:18 +0000</pubDate>
      <link>https://dev.to/francofuji/ai-agents-need-real-email-infrastructure-9m3</link>
      <guid>https://dev.to/francofuji/ai-agents-need-real-email-infrastructure-9m3</guid>
      <description>&lt;h2&gt;
  
  
  The Paradox at the Heart of AI Automation
&lt;/h2&gt;

&lt;p&gt;An AI agent can browse the web, write code, summarize a 300-page PDF, and call APIs on your behalf. It can reason through multi-step problems and generate plans that would take a human hours to produce.&lt;/p&gt;

&lt;p&gt;Ask it to create an account on a SaaS platform and it stalls. Not because the task is complex — but because the platform sends a confirmation email, and the agent has no inbox.&lt;/p&gt;

&lt;p&gt;This is the gap nobody talks about. AI agents are getting smarter every week, but they still can't complete one of the most basic flows in modern software: email-gated account creation. The intelligence is there. The infrastructure is missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem: Email Is a Gate, Not a Feature
&lt;/h2&gt;

&lt;p&gt;Email verification is not optional in production systems. It is the primary identity checkpoint for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Account creation&lt;/strong&gt; — nearly every SaaS platform requires a confirmed email before granting access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTP verification&lt;/strong&gt; — two-factor authentication, magic link login, password reset flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactional onboarding&lt;/strong&gt; — license keys, trial activation, API credential delivery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription confirmation&lt;/strong&gt; — billing platforms send confirmation links before activating accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an AI agent needs to interact with any of these systems autonomously, it faces a structural problem. It lacks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity&lt;/strong&gt; — agents don't have their own email address by default. They borrow one, which creates collisions when running parallel tasks or test suites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inboxes&lt;/strong&gt; — even if you hand an agent an email address, it needs programmatic access to read what arrives. Human inboxes (Gmail, Outlook) require OAuth flows and UI scraping. Neither is designed for machines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistence&lt;/strong&gt; — the agent needs to know when the email arrives, not just poll indefinitely. Real-time notification matters. Polling with backoff on a consumer webmail service is fragile at best.&lt;/p&gt;

&lt;p&gt;Without these three things, any agent that touches email-gated systems is blocked at the first gate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Workarounds (And Why They Fail)
&lt;/h2&gt;

&lt;p&gt;Developers have been improvising solutions for years. None of them are production-grade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mocks and stubs&lt;/strong&gt; skip email delivery entirely in tests. They pass locally and fail in staging because no real SMTP path is exercised. You ship a broken confirmation flow and find out when a user reports it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared inboxes&lt;/strong&gt; (a team Gmail or a catch-all alias) work for manual testing. They break immediately when multiple agents or test workers run in parallel. Message routing is ambiguous — you cannot guarantee which worker reads which email. And shared inboxes accumulate thousands of messages that have to be manually pruned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer disposable email services&lt;/strong&gt; like Mailinator or Guerrilla Mail have no real API. They expose a web UI. To automate them, you need a scraper on top of a scraper — fragile, rate-limited, and frequently blocked by platforms as known spam domains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual flows&lt;/strong&gt; don't scale. If your CI pipeline needs to verify email-based signup 50 times a day, you cannot have a human in the loop.&lt;/p&gt;

&lt;p&gt;The pattern is always the same: workaround works locally, breaks under load, breaks under parallelism, breaks when the target platform updates its UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Insight: Email Is Not UI. It's Infrastructure.
&lt;/h2&gt;

&lt;p&gt;This is the framing shift that matters.&lt;/p&gt;

&lt;p&gt;Most developers treat email verification as part of the user experience — a UI concern, something to mock in tests and tolerate in production. But email is a protocol. SMTP has been running since 1982. It is infrastructure in the same sense that DNS or TCP/IP is infrastructure.&lt;/p&gt;

&lt;p&gt;When you need to interact with a system that uses email as a control plane — and virtually every SaaS platform does — you need an email layer that behaves like infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Addressable: you can create and destroy addresses on demand&lt;/li&gt;
&lt;li&gt;Observable: you can receive events when mail arrives, not just poll&lt;/li&gt;
&lt;li&gt;Authenticated: access is controlled per inbox, not shared&lt;/li&gt;
&lt;li&gt;Ephemeral: addresses expire cleanly so you don't accumulate noise&lt;/li&gt;
&lt;li&gt;Programmable: the entire lifecycle is driven by an API, not a browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consumer disposable email services are none of these things. A real temporary email API is all of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  How uncorreotemporal.com Solves This
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt; is a programmable disposable email API built as infrastructure from the ground up. The architecture has four layers that work together:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SMTP ingestion&lt;/strong&gt; — a real SMTP server built on &lt;code&gt;aiosmtpd&lt;/code&gt; that receives actual email on port 25. Not a webhook relay, not a polling scraper. When a platform sends a confirmation email, it lands via standard SMTP delivery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastAPI backend&lt;/strong&gt; — an async REST API (FastAPI + SQLAlchemy 2.0 async + PostgreSQL) that manages mailbox lifecycle. You create, read, and delete mailboxes through HTTP endpoints. Mailboxes are per-owner, quota-enforced by plan, and isolated from each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-time WebSocket stream&lt;/strong&gt; — when mail arrives, a Redis pub/sub event is published to the channel &lt;code&gt;mailbox:{address}&lt;/code&gt;. Any client subscribed to &lt;code&gt;WS /ws/inbox/{address}&lt;/code&gt; receives a &lt;code&gt;{"event": "new_message", "message_id": "uuid"}&lt;/code&gt; notification immediately. No polling required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background expiry loop&lt;/strong&gt; — &lt;code&gt;core/expiry.py&lt;/code&gt; runs an async loop every 60 seconds that marks expired mailboxes &lt;code&gt;is_active=False&lt;/code&gt;. Expiration is deterministic: you set the TTL when you create the inbox, and it expires on schedule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP server&lt;/strong&gt; — an MCP server (&lt;code&gt;FastMCP&lt;/code&gt;) that wraps the entire email flow as tools for AI agents. Tools include &lt;code&gt;create_signup_inbox&lt;/code&gt;, &lt;code&gt;wait_for_verification_email&lt;/code&gt;, &lt;code&gt;get_latest_email&lt;/code&gt;, &lt;code&gt;extract_otp_code&lt;/code&gt;, &lt;code&gt;extract_verification_link&lt;/code&gt;, and &lt;code&gt;complete_signup_flow&lt;/code&gt;. An agent can complete the entire verification cycle in one tool call.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Example: AI Agent Completing an Email Signup Flow
&lt;/h2&gt;

&lt;p&gt;Here is a working Python example using the REST API directly. This is the same flow the MCP &lt;code&gt;complete_signup_flow&lt;/code&gt; tool executes internally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://uncorreotemporal.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-uct-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: Create a temporary inbox
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/mailboxes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mailbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&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="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mailbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;address&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mailbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expires_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Inbox created: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (expires: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Use this address to sign up somewhere
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Using &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to sign up on the target platform...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 3: Poll for the verification email
&lt;/span&gt;&lt;span class="n"&gt;message_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# 90 seconds max
&lt;/span&gt;    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;message_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Email arrived: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Verification email did not arrive within timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 4: Read the full message
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/mailboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&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="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body_html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

&lt;span class="c1"&gt;# Step 5: Extract OTP or confirmation link
&lt;/span&gt;&lt;span class="n"&gt;otp_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(\d{4,8})\b&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;link_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https?://[^\s&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;gt;]+confirm[^\s&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, body)

if otp_match:
    print(f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="n"&gt;OTP&lt;/span&gt; &lt;span class="n"&gt;extracted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;otp_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
if link_match:
    print(f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="n"&gt;Verification&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;link_match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)

# Step 6: Clean up
httpx.delete(f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mailboxes&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, headers=headers)
print(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="n"&gt;Inbox&lt;/span&gt; &lt;span class="n"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a full, copy-paste ready implementation. The same logic runs inside the MCP &lt;code&gt;complete_signup_flow&lt;/code&gt; tool — but from the agent's perspective, the entire flow collapses to a single tool call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Via MCP (for Claude, GPT-4 tools, or any MCP-compatible agent)
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete_signup_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;              &lt;span class="c1"&gt;# the inbox address used
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verification_link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# extracted confirmation URL
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otp_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;           &lt;span class="c1"&gt;# extracted numeric code if present
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP tool handles inbox creation, waiting, reading, extraction, and returns a structured result. The agent never touches the email protocol directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters for AI Agents Specifically
&lt;/h2&gt;

&lt;p&gt;The shift toward autonomous AI agents changes what infrastructure means.&lt;/p&gt;

&lt;p&gt;When a human uses a SaaS tool, they provide their identity: their email, their phone, their browser session. The system is designed for a single authenticated human interacting over time.&lt;/p&gt;

&lt;p&gt;When an AI agent uses a SaaS tool, it needs to provision identity on demand. It may need to sign up for ten accounts in parallel, verify each one, complete onboarding, and then discard the accounts. It may need to run this flow in CI every night. None of this maps to a human identity model.&lt;/p&gt;

&lt;p&gt;Email gives agents an identity layer that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ephemeral by design&lt;/strong&gt; — temporary inboxes expire automatically, leaving no permanent footprint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel-safe&lt;/strong&gt; — each run gets its own isolated inbox with its own authentication token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programmatically observable&lt;/strong&gt; — the agent can subscribe to real-time events instead of polling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatible with real systems&lt;/strong&gt; — because it uses standard SMTP, every platform that sends email will work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not about teaching agents to use email like a human. It is about giving agents access to email as infrastructure — the same way they have access to HTTP clients, databases, and file systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Developer Use Cases
&lt;/h2&gt;

&lt;p&gt;The immediate applications are practical and concrete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD email testing&lt;/strong&gt; — your pipeline creates a real inbox, triggers a signup flow on staging, reads the confirmation email, and asserts on the link format or OTP structure. No shared accounts, no mocked delivery. The test exercises the real SMTP path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End-to-end testing with Playwright or Selenium&lt;/strong&gt; — browser automation scripts that need to complete signup flows can receive real emails. The test gets a fresh inbox per run, uses the address in the form, reads the verification email, and clicks the link.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent onboarding flows&lt;/strong&gt; — an agent tasked with "create an account on platform X and retrieve the API key" can complete the full flow autonomously: inbox creation, signup form submission, email reading, OTP entry, API key extraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated scraping with auth&lt;/strong&gt; — data collection pipelines that require account creation at each target site. Each run gets a fresh identity and a fresh inbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel test isolation&lt;/strong&gt; — multiple test workers run simultaneously without shared state. Each worker creates its own mailbox, verifies against it, and deletes it. No race conditions, no message routing ambiguity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent-assisted QA&lt;/strong&gt; — QA automation agents that verify transactional email content (formatting, links, subject lines) as part of a deployment gate. Every deploy triggers a full email lifecycle check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The current conversation about AI agents focuses almost entirely on reasoning capability: how well the model understands instructions, how reliably it uses tools, how it handles ambiguity. That is the right conversation to have — but it is incomplete.&lt;/p&gt;

&lt;p&gt;Agents fail not just because they reason poorly. They fail because they cannot interface with real-world systems that require an identity to function. Email verification is not an edge case. It is the default access control mechanism for virtually every SaaS platform on the internet.&lt;/p&gt;

&lt;p&gt;The solution is not to mock it away or build brittle scrapers on top of consumer disposable email services. The solution is to treat email as infrastructure — addressable, observable, authenticated, and ephemeral — and expose it through an API that agents can call directly.&lt;/p&gt;

&lt;p&gt;AI agents do not need more intelligence to complete an email signup flow. They need a real inbox.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt; is that inbox.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
    </item>
    <item>
      <title>How to Test Email Signup Flows in CI/CD Pipelines</title>
      <dc:creator>Francisco Perez</dc:creator>
      <pubDate>Mon, 30 Mar 2026 22:19:52 +0000</pubDate>
      <link>https://dev.to/francofuji/how-to-test-email-signup-flows-in-cicd-pipelines-179c</link>
      <guid>https://dev.to/francofuji/how-to-test-email-signup-flows-in-cicd-pipelines-179c</guid>
      <description>&lt;h2&gt;
  
  
  The Pain Every CI Pipeline Knows
&lt;/h2&gt;

&lt;p&gt;It works locally. The signup form submits, the confirmation email arrives in your inbox, you paste the OTP, it verifies. Ship it.&lt;/p&gt;

&lt;p&gt;Then CI runs. The test sends the signup request, polls... and times out. Or worse: it passes because you mocked the email sender and never actually tested that the email arrives, renders correctly, or contains a working code.&lt;/p&gt;

&lt;p&gt;Email signup flows break in CI for a handful of predictable reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared inboxes&lt;/strong&gt; — two parallel test runs both register and poll the same address. Whichever test gets the email first passes; the other times out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mocked senders&lt;/strong&gt; — you assert that &lt;code&gt;send_email()&lt;/code&gt; was called, not that an email was received. Template bugs, SMTP credential expiry, and malformed links all pass through silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brittle fixtures&lt;/strong&gt; — hardcoded email addresses that accumulate stale messages across runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No isolation&lt;/strong&gt; — there's no API to create a fresh inbox per test run, so you bolt on a shared test account and cross your fingers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is not a better mock. It's a real, isolated, programmable inbox that your test owns for its lifetime.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;All examples in this article are from the &lt;a href="https://github.com/francofuji/temporary-email-api-examples" rel="noopener noreferrer"&gt;&lt;code&gt;temporary-email-api-examples&lt;/code&gt;&lt;/a&gt; repo. The &lt;a href="https://github.com/francofuji/temporary-email-api-examples/tree/main/python-example" rel="noopener noreferrer"&gt;&lt;code&gt;python-example/&lt;/code&gt;&lt;/a&gt; folder is a single-file script that demonstrates the full flow with plain &lt;code&gt;requests&lt;/code&gt; — no extra libraries required.&lt;/p&gt;

&lt;p&gt;Set two environment variables before running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UCT_API_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://uncorreotemporal.com/api/v1"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UCT_EMAIL_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_api_key_here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/francofuji/temporary-email-api-examples
&lt;span class="nb"&gt;cd &lt;/span&gt;temporary-email-api-examples/python-example
pip &lt;span class="nb"&gt;install &lt;/span&gt;requests
python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see a temporary inbox created, a polling loop start, and — once you trigger a real email to the address — an OTP extracted and printed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Flow: Create Inbox - Wait for Email - Extract OTP
&lt;/h2&gt;

&lt;p&gt;The pattern is three steps. Here is how the example implements each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the Inbox
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UCT_API_BASE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UCT_EMAIL_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;create_resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/inboxes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;create_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;inbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_resp&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="n"&gt;inbox_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One POST, one inbox. No signup flow, no shared state. The &lt;code&gt;inbox_id&lt;/code&gt; is the key you use for the next two steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Poll for Messages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(\d{6})\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Timed out waiting for OTP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msg_resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/inboxes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;inbox_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&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="n"&gt;msg_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;msg_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg_resp&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="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Retrying...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop handles three distinct cases: server errors (5xx, which you retry), request failures (network blips), and an empty inbox (normal while waiting). Separating these is what makes polling reliable in CI — you don't bail on transient failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Extract the OTP
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&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;if&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OTP found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;otp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The regex &lt;code&gt;\b(\d{6})\b&lt;/code&gt; matches any standalone 6-digit sequence. Adjust the pattern to match your application's format.&lt;/p&gt;




&lt;h2&gt;
  
  
  CI/CD Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Set an explicit timeout and fail loudly.&lt;/strong&gt; The example uses 60 seconds. In CI, an infinite loop or a silently hanging test is worse than a hard failure. If the email doesn't arrive in 60 seconds, something upstream is broken — raise, don't sleep forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use fixed poll intervals, not adaptive backoff.&lt;/strong&gt; Poll every 3 seconds. Predictable timing makes failures easier to diagnose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handle 5xx separately from 4xx.&lt;/strong&gt; A 500 from the messages endpoint is probably transient. A 401 or 404 means your credentials or inbox ID are wrong — retrying won't help. The example retries 5xx and raises immediately on 4xx via &lt;code&gt;raise_for_status()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't share inbox IDs across test cases.&lt;/strong&gt; Create one inbox per test run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Store credentials in CI secrets, not in code.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;UCT_API_BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.UCT_API_BASE_URL }}&lt;/span&gt;
  &lt;span class="na"&gt;UCT_EMAIL_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UCT_EMAIL_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Works in CI
&lt;/h2&gt;

&lt;p&gt;Most email testing approaches fail in CI because they depend on something external: a shared inbox, a stubbed SMTP server, or a hardcoded test account that accumulates state across runs.&lt;/p&gt;

&lt;p&gt;This approach is stateless from the pipeline's perspective. Each run calls &lt;code&gt;POST /inboxes&lt;/code&gt;, gets a fresh address and ID back, and owns that inbox exclusively until the test exits or the TTL expires. There's nothing to clean up, nothing to coordinate between parallel jobs, and no leftover messages from a previous run to confuse the polling logic.&lt;/p&gt;

&lt;p&gt;The inbox also receives real SMTP delivery — the same delivery path your production email goes through. If your email provider is down, your template is broken, or your SMTP credentials expired, the test will fail. That's the point. A test that can't catch those failures isn't testing email; it's testing whether your mock function was called.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Email signup testing is broken in most CI pipelines not because it's technically hard but because the default tools — mocks, shared inboxes, SMTP dev servers — don't give you the properties you need: isolation, real delivery, and an API to read the result programmatically.&lt;/p&gt;

&lt;p&gt;The pattern in this article is straightforward: create a dedicated inbox per test, poll for the message with a hard timeout, extract the OTP or link, and proceed. The &lt;a href="https://github.com/francofuji/temporary-email-api-examples/tree/main/python-example" rel="noopener noreferrer"&gt;Python example&lt;/a&gt; is a working implementation you can drop into any pipeline today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse the full &lt;a href="https://github.com/francofuji/temporary-email-api-examples" rel="noopener noreferrer"&gt;&lt;code&gt;temporary-email-api-examples&lt;/code&gt;&lt;/a&gt; repo for Node.js, shell/cURL, Playwright, and pytest examples&lt;/li&gt;
&lt;li&gt;Get your API key at &lt;a href="https://uncorreotemporal.com" rel="noopener noreferrer"&gt;uncorreotemporal.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Wrap the flow in a pytest fixture for per-test inbox isolation in larger test suites&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>testing</category>
      <category>python</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
