This post is partly a mini-guide, partly a note to my future self who will probably forget how this worked.
Like many developers, I don’t want to give my main email address to random websites just to receive a one-time verification code (OTP).
Because let’s be honest:
you verify once… and then you receive newsletters forever.
So I built a simple inbound email tool using:
Python-Flask
DNS (MX records)
A bit of regex magic ✨
What This Tool Does (In Short)
Works like a temporary email inbox
Supports HTML and plain-text emails
Extracts OTP / verification codes
Forwards attachments if any
Before You Start: You Need Your Own Domain
Before anything else, you need your own domain.
SendGrid Inbound Parse does not work with free email domains (like Gmail, Outlook, etc.).
You must own a domain so you can control the MX records.
In my case, I simply bought a domain from Namecheap.
Once you have your domain:
You can point MX records to SendGrid
Create addresses like noreply@your-domain.com
Fully control inbound email routing
Without a custom domain, this setup is not possible.
Step 1: Adding SendGrid DNS Records to Your Domain Provider
SendGrid does not manage your DNS.
Instead, it tells you which DNS records you must add, and you are responsible for adding them to your domain provider (Namecheap, GoDaddy, Cloudflare, etc.).
This section shows how to correctly transfer DNS records from SendGrid to your domain provider.
First, lets check our DNS Records on SendGrid.
In Manual Install section, SendGrid will display a list of DNS records such as:
⚠️ These records are instructions, not active DNS entries. We are gonna use these records, so take a note for next step.
Step 2: Open your domain provider’s DNS panel
For example, in Namecheap:
Go to Domain List

You will see now your all domain list from provider.Copy records exactly as shown from your Sendgrid DNS Records
For each record shown in SendGrid, create a matching record in your domain provider. Don't forget to add dots every CNAME Values.Save and wait for DNS propagation
DNS changes are not instant.Verify inside SendGrid
Once DNS propagates:
Return to Sender Authentication
Click Verify
Records should turn green
This means SendGrid can now authenticate emails sent from your domain.
Step 3: DNS Configuration (Important!)
This part trips people up... including me 🙃
MX Record (Domain Provider, e.g. Namecheap)
Add this MX record to your domain provider:

⏳ DNS propagation can take a few minutes to a few hours.
Yes, even if SendGrid says “Verified”.
You can check with:
Record type: MX
Domain: your-domain.com
Step 4: SendGrid Inbound Parse Setup
In SendGrid:
And then press Add
SendGrid will now expect emails sent to @your-domain.com and forward them to your Flask app.
Step 5: Flask App – Receiving Emails
Here’s the core logic. (Full project is on my GitHub)
from flask import Flask, request
from bs4 import BeautifulSoup
import re, base64
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Attachment, FileContent, FileName, FileType, Disposition
from_email = 'noreply@your-domain.com'
to_email = 'YOUR_EMAIL'
API = 'YOUR_API_KEY'
app = Flask(__name__)
otp = ''
_to = ''
sg = SendGridAPIClient(API)
Why these libraries?
Flask → Webhook receiver
BeautifulSoup → Extract text from HTML emails
re → OTP detection
SendGrid SDK → Forward emails
base64 → Handle attachments
Step 6: Inbound Email Endpoint
@app.route('/email', methods=['POST'])
def email_receiver():
This endpoint is called by SendGrid, not by a browser.
Reading Email Metadata
print('From:', request.form['from'])
print('To:', request.form['to'])
print('Subject:', request.form['subject'])
Useful for debugging and filtering later.
Step 7: Handling HTML Emails
if 'html' in request.form:
soup = BeautifulSoup(request.form['html'], 'html.parser')
text = soup.get_text()
if "Your verification code is as mentioned below" in text:
match = re.search(r'verification code.*?(\d{6})', text)
_to = request.form['to']
otp = match.group(1)
Once an email hits the endpoint, the service first converts any incoming HTML content into plain text. This makes the message easier to analyze and avoids dealing with messy markup.
After that, the text is scanned for a 6-digit verification code, which is typically how most OTP emails are structured. When a match is found, the code is extracted and stored so it can be forwarded or used immediately.
This approach keeps the logic simple and flexible, while still covering the majority of real-world verification emails.
Step 8: Handling Plain Text Emails
elif 'text' in request.form:
text = request.form['text']
if "Verification Code:" in text:
match = re.search(r'\b\d{4}\b', text)
_to = request.form['to']
otp = match.group() if match else ""
This covers emails without HTML (surprisingly common).
Step 9: Handling Attachments
attachments = []
for file_key in request.files.keys():
file = request.files[file_key]
file_bytes = file.read()
encoded = base64.b64encode(file_bytes).decode("utf-8")
Attachments are:
Read
Base64-encoded
Re-attached to the forwarded email
Step 10: Forward Everything to Main Email
message = Mail(
from_email = from_email,
to_emails = to_email,
subject = request.form['to'],
plain_text_content = text
)
for attachment in attachments:
message.add_attachment(attachment)
When forwarding the email to my main inbox, I keep things intentionally simple but useful. I use the temporary email address itself as the subject, so I can immediately see which site the message originally came from without opening the email.
Along with that, I forward the entire email content, including any attachments if they exist. This way, nothing is lost in translation — whether it’s a verification message, a receipt, or an attachment-based confirmation, everything arrives exactly as it was received.
Final Step: Send & Done
sg.send(message)
return 'OK', 200
If SendGrid receives 200 OK, you’re golden 🟢
Why I Built This?
I simply don’t want spam. The moment you give your real email address to a random website, your inbox slowly turns into a digital landfill.
I also don’t fully trust every site that asks for my email. Most of the time, I don’t even want to use their service long-term. I just need one thing: the OTP or verification code so I can move on.
This project was my way of creating a buffer between “random websites” and my real inbox. A disposable entry point that catches verification emails, extracts what I need, and forwards only the important part to my main email.
Full Source Code
Osman-Kahraman
/
mail_otp_receiver_base
Self-hosted disposable email system using SendGrid Inbound Parse and Flask. Receive OTP codes, parse emails (HTML/Text), and forward them with attachments without exposing your main email address.
mail_otp_receiver_base
mail_otp_receiver_base is a self-hosted disposable email (temp mail) system built with SendGrid Inbound Parse and Flask.
It allows you to receive emails on your own domain, automatically extract OTP / verification codes from both HTML and plain-text emails, and forward them — along with attachments — to your main inbox, without exposing your real email address to spammy websites.
I built this mainly to avoid giving my personal email to random services.
I’m also keeping this repo as a reminder for my future self — because I will forget how I set this up.
Features
- Self-Hosted Temp Mail – Works like popular temporary email services
- OTP Extraction – Automatically parses verification codes using regex
- HTML & Text Support – Handles both email formats
- Attachment Forwarding – Forwards attachments if present
- Custom Domain Support – Full control via your own domain
- Lightweight & Simple – Minimal setup, no database…
Deploying on Render (Sleeping Server? Still Fine)
This project can be deployed on Render, even using the free tier.
At first glance, Render’s free tier sounds risky because the server goes to sleep after a period of inactivity. Normally, that would raise concerns about availability and reliability. In this setup, however, it turns out to be a non-issue.
Incoming emails never depend on your application server being online. Thanks to the MX records, emails are routed directly to SendGrid, which fully handles mail delivery. By the time your Flask app becomes relevant, the email has already been accepted.
Your server’s only responsibility is post-processing. It parses the content, extracts OTP or verification codes, and forwards the data when available. Whether the server is awake, asleep, or restarting does not affect email delivery itself.
If the server happens to be asleep, nothing breaks. There is no “address not found” error, no rejected email, and no race condition waiting for a wake-up. The worst-case scenario is simply that parsing happens a bit later.
This separation of responsibilities is exactly why free-tier hosting works well here. Email delivery is handled by dedicated infrastructure, while your app adds logic on top. For a personal disposable email system or OTP-forwarding tool, this tradeoff is more than acceptable.
Closing Thoughts
This setup shows that not every backend needs to be online 24/7 to be reliable. By letting specialized services do what they are best at, you can simplify your architecture without sacrificing correctness.
Using Render’s free tier is not a compromise here. It’s simply a practical choice. The email pipeline remains stable, delivery is guaranteed, and your application adds value only where it matters.
Sometimes, the cleanest solution is the one that worries the least about uptime.
Lessons Learned
Email delivery and application logic should live in different layers. Once those responsibilities are separated, many common hosting concerns simply disappear.
Free-tier infrastructure can be perfectly viable when your system is event driven rather than request-driven. If nothing breaks when your server sleeps, then sleeping is not a problem.
Finally, designing around real constraints instead of fighting them often leads to simpler and more robust systems.
This post only explains the core logic.
If you’re tired of spam too, feel free to fork, improve, or just steal the idea :)
Happy hacking!!!









Top comments (2)
That seems cool. I liked how you go step by step and explain what happens. Keep going gang.🙌
Thank you ma g