📨 Email-AI Assistant using FastAPI, Gemini & Postmark
Pravesh Sudha

Pravesh Sudha @pravesh_sudha_3c2b0c2b5e0

About: AWS Community Builder Bridging critical thinking and innovation, from philosophy to DevOps.

Location:
India
Joined:
Jul 19, 2024

📨 Email-AI Assistant using FastAPI, Gemini & Postmark

Publish Date: Jun 4
34 15

A submission for the Postmark Challenge: Inbox Innovators


💡 What I Built

Hey folks! 👋

I built an Email-based AI Assistant powered by FastAPI, Gemini, and Postmark. The assistant allows users to send an email and get an AI-generated response right in their inbox — just like magic 🪄.

Here’s the workflow in simple terms:

User sends an email ➝ Postmark receives it ➝ Webhook (FastAPI backend) is triggered ➝
Gemini processes the email ➝ Response is generated ➝
Reply is sent back to the user via Postmark
Enter fullscreen mode Exit fullscreen mode

Image description


🎥 Live Demo

📧 Try it yourself:
Send an email to 👉 assistant@codewithpravesh.tech
Ask a question like “Explain Postmark in brief” and within 30–60 seconds, you’ll get an intelligent reply — straight to your inbox.

Image description

▶️ Watch the full walkthrough below
Watch the video


💻 Code Repository

The project is open-source and available on GitHub:
🔗 https://github.com/Pravesh-Sudha/dev-to-challenges

The relevant code lives in the postmark-challenge/ directory, containing:

  • main.py: Sets up the FastAPI server and webhook endpoint
  • utils.py: Handles Gemini integration and Postmark email sending logic

main.py

from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.responses import JSONResponse
from utils import get_response, send_email_postmark

app = FastAPI()

class PostmarkInbound(BaseModel):
    From: str
    Subject: str
    TextBody: str

@app.post("/inbound-email")
async def receive_email(payload: PostmarkInbound):
    sender = payload.From
    subject = payload.Subject
    body = payload.TextBody

    # Prevent infinite loop
    if sender == "assistant@codewithpravesh.tech":
        return {"message": "Self-email detected, skipping."}

    response = get_response(body)

    try:
        send_email_postmark(
            to_email=sender,
            subject=f"Re: {subject}",
            body=response
        )
    except Exception as e:
        print("Email send failed, but continuing:", e)

    return JSONResponse(content={"message": "Processed"}, status_code=200)
Enter fullscreen mode Exit fullscreen mode

utils.py

import os
import requests
import google.generativeai as genai
from dotenv import load_dotenv
load_dotenv()

genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
model = genai.GenerativeModel("models/gemini-2.5-flash-preview-04-17-thinking")

def get_response(prompt: str) -> str:
    try:
        response = model.generate_content(prompt)
        return response.text.strip()
    except Exception as e:
        return f"Error: {e}"

def send_email_postmark(to_email, subject, body):
    postmark_token = os.getenv('POSTMARK_API_TOKEN')
    payload = {
        "From": "assistant@codewithpravesh.tech",
        "To": to_email,
        "Subject": subject or "No Subject",
        "TextBody": body or "Empty Response",
    }

    headers = {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "X-Postmark-Server-Token": postmark_token
    }

    try:
        r = requests.post("https://api.postmarkapp.com/email", json=payload, headers=headers)
        r.raise_for_status()
    except Exception as e:
        print("Failed to send email via Postmark:", e)
Enter fullscreen mode Exit fullscreen mode

🛠️ How I Built It

This project has been a rewarding rollercoaster 🎢 — full of debugging, email loops, and a bit of DNS sorcery.

🚫 Problem: No Private Email

When I first registered on Postmark, I realized they don’t allow public email domains (like Gmail) for sending. I didn’t have a private email. 😓

✅ Solution: Dev++ to the Rescue

I reached out to the Dev.to team, and they kindly gifted me a DEV++ membership 💛 — which included a domain and two private emails!

Image description

I registered:
🔗 codewithpravesh.tech
📬 Created user@codewithpravesh.tech

Using this, I successfully created a Postmark account. ✅


🧠 Choosing the LLM

I wanted a fast, reliable, and free LLM. I tested:

  • ❌ OpenAI — Paid
  • ❌ Grok — Complicated setup
  • Gemini — Free via Google API, simple to use, fast response

The winner? 🏆 Gemini 2.5 Flash


🧪 Local Testing with Ngrok

To test the webhook, I spun up the FastAPI app locally and exposed it using ngrok.
Webhook URL used:

https://<ngrok-url>/inbound-email
Enter fullscreen mode Exit fullscreen mode

Then I set up Inbound Domain Forwarding on Postmark:

  • Added an MX Record pointing to inbound.postmarkapp.com in my domain DNS

Image description

  • Used assistant@codewithpravesh.tech as the receiver email
  • Faced 422 Error because my account approval was in pending state.

Image description


😅 The Loop Disaster

For testing, I tried sending an email from user@codewithpravesh.techassistant@codewithpravesh.tech.
Result? Infinite loop 🔁
Why?
My webhook was triggered, and it responded to itself over and over.

Image description

Outcome:

  • Burned through 100 free emails/month
  • Had to upgrade with promo code DEVCHALLENGE25

Fix:

if sender == "assistant@codewithpravesh.tech":
    return {"message": "Self-email detected, skipping."}
Enter fullscreen mode Exit fullscreen mode

Image description

  • Now application is working fine locally.

Image description


☁️ Deploying on AWS EC2

To make it public, I chose AWS EC2:

  • Instance type: t2.small
  • Storage: 16 GB
  • Elastic IP assigned
  • Security group: Open HTTP, HTTPS (0.0.0.0/0), SSH (my IP)

Image description

Then:

  1. 🧾 Cloned my GitHub repo
  2. 🧰 Installed nginx
  3. 🔧 Configured DNS A record to point app.codewithpravesh.tech ➝ EC2 IP

Image description


🔁 Nginx Reverse Proxy Setup

I created a file /etc/nginx/sites-available/email-ai-assistant:

server {
    listen 80;
    server_name app.codewithpravesh.tech;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
Enter fullscreen mode Exit fullscreen mode

Image description

Enabled it:

sudo ln -s /etc/nginx/sites-available/email-ai-assistant /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

Updated Postmark’s webhook URL to:

http://app.codewithpravesh.tech/inbound-email
Enter fullscreen mode Exit fullscreen mode

Image description


🧬 Making It Production-Ready

To keep the app alive after reboot, I created a systemd service:

[Unit]
Description=Email AI Assistant FastAPI App
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/dev-to-challenges/postmark-challenge
Environment="PATH=/home/ubuntu/dev-to-challenges/postmark-challenge/app-venv/bin"
ExecStart=/home/ubuntu/dev-to-challenges/postmark-challenge/app-venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000
Restart=always

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Image description

Enabled it using:

sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable email-assistant
sudo systemctl start email-assistant
Enter fullscreen mode Exit fullscreen mode

Cloud Testing

Image description


Last Minute things 😅

After posting the article, I got a lovely comment as shown below:
"Very Interesting!
But Privacy
"

Image description

To fix this, I get inside the instance and generate a SSL/TLS certificate for the Webhook URL using the following command:

sudo certbot --nginx -d app.codewithpravesh.tech
Enter fullscreen mode Exit fullscreen mode

Image description

and Voila!, everything got setup, it changes the Nginx config file (email-assistant) accordingly.

Image description

The only thing left to do was just just http to https in the webhook URL.

Image description


🙌 Final Thoughts

This was such a fun and technically challenging project!
Big thanks to Postmark and the Dev.to team for organizing this challenge and giving us a platform to innovate. 💛

I learned a ton about:

  • Webhooks & mail routing
  • FastAPI production setups
  • DNS + Postmark integration
  • Using LLMs in real-world tools

🧠 Try the app → assistant@codewithpravesh.tech
🎥 Watch the demo → YouTube Walkthrough

If you liked this project, leave a ❤️, star the repo, and feel free to reach out on Twitter or LinkedIn.

Comments 15 total

  • Nevo David
    Nevo DavidJun 5, 2025

    Pretty cool seeing how you actually battled through those mail loops and DNS headaches – respect for just sticking with it and getting it all to work.

    • Pravesh Sudha
      Pravesh SudhaJun 5, 2025

      Thanks buddy, Mail loop was actually a big one!

  • Dotallio
    DotallioJun 5, 2025

    Loved the story about debugging that email loop - felt that pain before! Any cool use cases for this beyond quick answers or summaries?

    • Pravesh Sudha
      Pravesh SudhaJun 5, 2025

      Thanks! That loop had me sweating 😅. Beyond summaries, we can extend the program by adding features like auto-reply for customer support, newsletter digests, or even daily briefings—basically turning email into a lightweight AI interface.

  • Francesco Larossa
    Francesco LarossaJun 5, 2025

    Very interesting!
    But... Privacy?

    • Pravesh Sudha
      Pravesh SudhaJun 5, 2025

      For security, the only traffic we allow is HTTP access (all over) and SSH (from my IP), the only thing I forgot to add is SSL/TLS Certificate. Will do it soon!

  • Parag Nandy Roy
    Parag Nandy RoyJun 6, 2025

    Super cool build, Pravesh!

  • Dupe
    DupeJun 9, 2025

    Great, that's interesting.

  • Jakov
    JakovJun 10, 2025

    Thanks for the post Pravesh, very interesting stuff, learned quite a bit reading it.
    Just a note: Viola is an instrument, I think you meant Voila :)

  • John
    JohnJun 10, 2025

    Hello buddy! take your instant about $15 in DuckyBSC crypto right now! — Click here! Claim by connecting your wallet. 👉 duckybsc.xyz

Add comment