Skip to content

SendGrid Inbound Parse Webhook: Receive & Process Incoming Email

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about SendGrid Inbound Parse Webhook: Receive & Process Incoming Email. We cover key concepts, practical examples, and best practices to help you master this topic.

SendGrid Inbound Parse Webhook receives incoming emails sent to your domain, parses them into structured data (headers, body, attachments), and POSTs to your endpoint.

What You'll Learn

How to configure the Inbound Parse Webhook, receive and parse incoming email, extract attachments, handle reply-to workflows, and build email-to-ticket systems.

Why It Matters

Processing incoming email enables powerful workflows: support@ becomes a ticket system, replies auto-attach to conversations. DodaTech uses inbound Parsing so users can reply to threat alerts and their responses are logged to the incident.

Real-World Use

A user replies to a threat alert email. SendGrid parses the reply, POSTs to the webhook, which logs the response as a comment on the security incident.

flowchart LR
    A["User Replies\nto Alert Email"] --> B["MX Record\nPoints to SendGrid"]
    B --> C["SendGrid\nParse Email"]
    C --> D["POST to\nYour Webhook"]
    D --> E["Extract\n+ Parse"]
    E --> F["Store Reply\non Incident"]
    E --> G["Auto-Reply\nto User"]
    style A fill:#dbeafe,stroke:#2563eb
    style C fill:#1a82e2,color:#fff
    style D fill:#fef3c7,stroke:#d97706

Configuring Inbound Parse

# 1. Add MX record in DNS:
# Type: MX
# Name: inbound.yourdomain.com
# Value: mx.sendgrid.net
# Priority: 10

# 2. In SendGrid Dashboard:
# Settings > Inbound Parse
# Hostname: inbound.yourdomain.com
# URL: https://api.dodatech.com/sendgrid/inbound
# Spam check: On
# Send raw: Off

Receiving Parsed Email

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.route("/sendgrid/inbound", methods=["POST"])
def handle_inbound():
    # Extract fields from multipart form data
    to_email = request.form.get("to")
    from_email = request.form.get("from")
    subject = request.form.get("subject")
    text_body = request.form.get("text")
    html_body = request.form.get("html")
    attachments_count = int(request.form.get("attachments", 0))

    print(f"Email received from {from_email}")
    print(f"Subject: {subject}")
    print(f"Body: {text_body[:200]}...")

    # Process attachments
    for i in range(attachments_count):
        attachment = request.files.get(f"attachment{i}")
        if attachment:
            filename = attachment.filename
            content_type = attachment.content_type
            print(f"  Attachment {i}: {filename} ({content_type})")

    # Store in database
    # db.incoming_emails.insert_one({
    #     "from": from_email,
    #     "subject": subject,
    #     "body": text_body,
    #     "received_at": datetime.utcnow()
    # })

    return jsonify({"status": "ok"}), 200

Parsing Reply Threads

import re

def parse_reply(text_body):
    # Remove original message (common email clients)
    lines = text_body.split("\n")
    reply_lines = []

    for line in lines:
        # Stop at common reply markers
        if re.match(r"^On .+ wrote:", line.strip()):
            break
        if re.match(r"^_{10,}", line.strip()):
            break
        if line.strip().startswith("> "):
            continue
        reply_lines.append(line)

    reply_text = "\n".join(reply_lines).strip()
    return reply_text

@app.route("/sendgrid/inbound", methods=["POST"])
def handle_reply():
    from_email = request.form.get("from")
    text_body = request.form.get("text", "")
    subject = request.form.get("subject", "")
    references = request.form.get("references", "")

    reply_text = parse_reply(text_body)
    print(f"Parsed reply from {from_email}: {reply_text[:100]}...")

    # Extract incident ID from subject or references
    incident_id = extract_incident_id(subject, references)

    if incident_id:
        add_reply_to_incident(incident_id, from_email, reply_text)
        print(f"Reply added to incident: {incident_id}")
    else:
        create_new_ticket(from_email, reply_text, subject)
        print(f"New ticket created from email reply")

    return jsonify({"status": "ok"}), 200

def extract_incident_id(subject, references):
    match = re.search(r"\[INC-(\d+)\]", subject)
    if match:
        return match.group(1)
    # Fallback: check references header for message IDs
    return None

Processing Attachments

import os
from werkzeug.utils import secure_filename

UPLOAD_DIR = "/tmp/email_attachments"

def process_attachments(request, incident_id):
    attachments_count = int(request.form.get("attachments", 0))
    saved_files = []

    for i in range(attachments_count):
        attachment = request.files.get(f"attachment{i}")
        if attachment and attachment.filename:
            filename = secure_filename(f"{incident_id}_{int(time.time())}_{attachment.filename}")
            filepath = os.path.join(UPLOAD_DIR, filename)
            attachment.save(filepath)
            saved_files.append({
                "original_name": attachment.filename,
                "saved_as": filename,
                "content_type": attachment.content_type,
                "size": os.path.getsize(filepath)
            })
            print(f"Saved attachment: {filename} ({attachment.content_type})")

    return saved_files

Common Mistakes

1. DNS Misconfiguration

The MX record must point to mx.sendgrid.net with priority 10. Incorrect DNS means emails never reach SendGrid for parsing.

2. Not Handling Multipart Form Data

Inbound Parse sends multipart/form-data, not JSON. Use request.form for fields and request.files for attachments.

3. Ignoring Spam Check Results

Enable spam checking and check spam_score and spam_report fields. Process spam emails differently or discard them.

4. Processing Attachments Synchronously

Attachments can be large. Save them asynchronously or stream to cloud storage to avoid webhook timeout.

5. Not Handling Email Encoding

Emails may have different character encodings (UTF-8, ISO-8859-1). Handle encoding properly to avoid garbled text.

Practice Questions

  1. How do you configure DNS for Inbound Parse?
  2. What data format does the webhook use?
  3. How do you extract the sender's email address?
  4. How do you handle email attachments?

Answers:

  1. Add an MX record: hostname = inbound.yourdomain.com, value = mx.sendgrid.net, priority = 10.
  2. The webhook sends multipart/form-data with form fields (to, from, subject, text, html, etc.) and file fields (attachments).
  3. The from field in the form data contains the sender's email address as a string.
  4. Attachments arrive as file fields (attachment0, attachment1, etc.). Access via request.files.get("attachment0") and save or process immediately.

Challenge: Build an email-to-ticket system: configure Inbound Parse, parse incoming emails into tickets or replies, handle attachments (store in cloud storage), detect reply-vs-new-email, and store the parsed data in a database with full-text search.

FAQ

What email formats does Inbound Parse support?

Inbound Parse supports text/plain, text/html, and multipart (with attachments). It parses headers, body, and MIME parts.

How does Inbound Parse handle spam?

Enable the spam check option. Parsed data includes spam_score and spam_report fields. Emails with scores above a threshold can be filtered.

Can I receive emails with attachments?

Yes, attachments up to 20MB are parsed. Attachments are available as file fields in the multipart form data.

What is the difference between Parse Webhook and Event Webhook?

Parse Webhook handles incoming email (replies, new emails). Event Webhook handles outgoing email events (delivery, opens, bounces).

How many inbound emails can I process?

Inbound Parse pricing depends on your plan. The free tier includes 1000 inbound emails/month. Paid plans offer higher limits.

Mini Project

Build an email-to-incident system: configure Inbound Parse with MX record, parse incoming emails into structured data, detect replies vs new emails (using subject/References header), save attachments to cloud storage, and store parsed emails in a database searchable by sender and subject.

What's Next

SendGrid v3 API Deep Dive — explore the full REST API for advanced operations.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro