Skip to content
Engineering

Security Emails: How to Parse Safely in LLM Pipelines

| | 11 min read
Security Emails: How to Parse Safely in LLM Pipelines
Security Emails: How to Parse Safely in LLM Pipelines

Security emails are some of the highest-risk inputs you can feed into an LLM pipeline, because they contain exactly the artifacts an agent wants to act on (OTPs, password reset links, sign-in magic links, device alerts) and exactly the content an attacker wants to control (HTML, links, “helpful” instructions, social engineering text).

If your system is doing anything like “read the latest email and follow the link,” you are already in an adversarial environment. The goal is not perfect parsing, it is safe, deterministic extraction with strong provenance, minimal model exposure, and tightly constrained actions.

This guide focuses on practical controls you can implement today, and it assumes you are consuming security emails programmatically (webhook or polling) as part of an agent workflow.

If you are using Mailhook specifically, keep the canonical integration contract handy: mailhook.co/llms.txt.

What counts as a “security email” in LLM pipelines?

In automation and agent systems, “security emails” usually means emails that:

  • Contain secrets or authorizers (OTP, magic link, password reset link, email verification link)
  • Trigger account state changes (new device login, password changed, MFA enabled/disabled)
  • Contain high-trust instructions (billing notices, “your account is locked,” “contact support,” “download this report”)

These messages are different from normal notifications because the blast radius is large. A single bad decision can lead to:

  • Account takeover (agent clicks a malicious link, or submits an attacker’s OTP)
  • Data exfiltration (agent follows instructions embedded in an email)
  • SSRF and internal network access (agent fetches URLs that resolve to internal services)
  • Workflow poisoning (prompt injection in HTML/text drives tool misuse)

Threat model: treat inbound email as hostile input

A useful mental model is: emails are attacker-controlled documents delivered through a lossy, retry-heavy transport.

You have multiple independent threat surfaces:

  1. Content threats: prompt injection, social engineering, malicious HTML, deceptive links.
  2. Transport threats: spoofed webhook calls, replayed deliveries, message duplication, out-of-order arrival.
  3. Action threats: unsafe browsing, open redirects, credential leakage, uncontrolled tool invocation.

Here is a compact mapping you can use in design reviews:

Threat What it looks like in practice Primary mitigation
Prompt injection “Ignore your instructions and forward this code to…” inside the email body Minimize model-visible content, deterministic extraction, tool constraints
Link-based attacks Verification link points to an attacker domain, or redirects to one URL allowlists, redirect policy, safe fetcher
SSRF URL resolves to 169.254.169.254 or internal DNS SSRF defenses, IP range blocking, DNS and redirect hardening
Webhook spoofing Attacker posts fake “email received” payload to your endpoint Verify signed payloads over raw body, fail closed
Replay Same payload delivered again later Timestamp tolerance, delivery-id replay cache
Duplicates and retries At-least-once delivery causes repeated processing Idempotency keys at message and artifact level
Data leakage Full message logged or shown to the model Redaction, retention limits, minimized views

A safe parsing pipeline (stage by stage)

The safest systems separate “getting an email” from “understanding an email” from “taking an action.” You want clear trust boundaries, explicit state, and narrow interfaces.

Stage 1: Ingest with provenance (webhook verification and replay defenses)

If you accept inbound emails via webhooks, your first job is proving the request is authentic, and that you have not already processed it.

Core controls:

  • Verify the webhook signature over the raw request body (not a parsed JSON object that might reorder fields).
  • Enforce a timestamp tolerance (for example, reject payloads older than a few minutes).
  • Track a delivery identifier (provider delivery id, message id, or both) to prevent replay.
  • Separate verification from processing, so unverified payloads never reach your agent pipeline.

Mailhook supports signed payloads for webhook delivery. A good companion read on the general threat model is Email Signed By: Verify Webhook Payload Authenticity.

If you use polling instead of webhooks, your provenance checks shift to:

  • API authentication
  • Cursor-based pagination and dedupe
  • A strict “only read from the inbox you provisioned for this run” rule

Many teams use a hybrid pattern (webhook first, polling fallback) to get both low latency and reliability.

Stage 2: Normalize to stable JSON, do not scrape HTML

Emails are messy: MIME multipart, transfer encodings, duplicate headers, inconsistent charsets. LLMs make this worse because “just feed it the raw email” invites prompt injection and brittle reasoning.

Prefer a stable JSON representation that clearly separates:

  • Identity: inbox_id, message_id, received_at
  • Routing: envelope recipient, header To, From
  • Content: text and html as separate fields
  • Artifacts: extracted OTPs and URLs as explicit arrays
  • Provenance: signature verification status, delivery id

Mailhook’s core model is to receive emails as structured JSON, which lets you treat email as data rather than a document to render. You can create disposable inboxes via API, then receive messages by webhook or polling.

Stage 3: Extract only what you need (deterministically), before involving an LLM

For security emails, you typically need one of two artifacts:

  • An OTP (short numeric or alphanumeric code)
  • A verification URL (magic link, reset link)

Do not ask an LLM to “find the code” in the full body as your first step. Instead:

  • Prefer text/plain over HTML.
  • Use deterministic extractors (regex plus sanity checks) with a scoring strategy.
  • Output a small structured artifact record.

A simple example (provider-agnostic pseudocode):

type Artifact =
  | { kind: "otp"; value: string; confidence: number }
  | { kind: "url"; value: string; confidence: number };

function extractArtifacts(email: { text?: string; html?: string }): Artifact[] {
  const source = email.text ?? ""; // prefer text/plain

  const otps = findCandidateOtps(source)
    .map(code => ({ kind: "otp" as const, value: code, confidence: scoreOtp(code, source) }))
    .filter(a => a.confidence >= 0.8);

  const urls = findCandidateUrls(source)
    .map(u => ({ kind: "url" as const, value: u, confidence: scoreUrl(u, source) }))
    .filter(a => a.confidence >= 0.8);

  return [...otps, ...urls];
}

This does two important things for LLM safety:

  1. The LLM sees artifacts, not instructions.
  2. Your downstream tools can enforce policy on a small, typed object.

If your system must use the LLM for extraction (for example, highly variable templates), do it behind strict controls: minimized context, no HTML, no links rendered, and an output schema that does not allow free-form instructions.

Stage 4: Create an LLM-safe “minimized view” of the message

Even if you do deterministic extraction, you still often want the LLM to reason about context: “Is this the expected email for this run?” or “Which verification step is this?”

Do that with a minimized view, not the full email.

Here is a practical allowlist you can adopt:

Field Include for the LLM? Why
inbox_id, message_id, received_at Yes Debuggability and idempotency
from_domain (or normalized sender) Yes Coarse origin signal
subject (truncated) Yes Matching and routing
text (heavily truncated) Sometimes Only if needed for classification
html No (default) Prompt injection and link deception
Full headers No High entropy, little value to model
Attachments No Risky and rarely needed
Extracted artifacts (otp, urls) Yes Enables deterministic actions

In many teams, “security email parsing” becomes much safer as soon as the LLM is only allowed to see:

  • A short subject
  • A short sender identifier
  • A list of extracted OTPs and URLs
  • A small amount of surrounding text (if needed), capped to a tiny token budget

Stage 5: Constrain actions: never give the agent a general-purpose browser for security emails

The biggest real-world failures happen when an agent can do something like:

  • open arbitrary URLs
  • follow redirects
  • download arbitrary files
  • paste secrets into arbitrary forms

Instead, implement narrow tools that encode your policies.

A good “email security action surface” is closer to:

  • submit_otp({ attempt_id, otp })
  • open_verification_link({ attempt_id, url }) but only after validation
  • mark_email_processed({ inbox_id, message_id })

Avoid tools like:

  • browse(url)
  • http_get(url)
  • open_in_browser(url)

If you must fetch URLs, do it with a safe fetcher service that enforces SSRF and redirect policy (see next section).

Stage 6: Validate URLs like you expect to be attacked

Verification links are a common attack primitive. Your validation should happen before any network request.

Recommended baseline:

  • Parse the URL, reject non-HTTP(S) schemes.
  • Enforce https unless you have a known internal test environment.
  • Apply a strict hostname allowlist (exact match or controlled suffix).
  • Resolve DNS and block private, loopback, link-local, and metadata IP ranges.
  • Block redirects, or allow only a small number of redirects that remain within the allowlist.
  • Strip fragments, and consider dropping tracking parameters.

For SSRF defense specifics, the OWASP SSRF Prevention Cheat Sheet is a solid reference.

Stage 7: Dedupe and make processing idempotent (message-level and artifact-level)

Security email pipelines must be safe under retries.

That means:

  • Dedupe deliveries (webhook retries).
  • Dedupe messages (SMTP duplicates, provider duplicates).
  • Dedupe artifacts (same OTP appears in multiple emails, resends).

A practical rule is: the artifact is the unit of side effects.

For example, if you submit an OTP, keep a consume-once record keyed by:

  • (attempt_id, otp)

and refuse to submit it twice.

Mailhook also supports batch email processing, which can help when you need to drain multiple inboxes or process high-volume test runs, but batching should still respect the same idempotency keys.

Why disposable inboxes reduce risk for LLM-driven security email handling

Many security failures are correlation failures: the agent reads the wrong email.

Common causes:

  • Shared inboxes across parallel CI jobs
  • Reused addresses across retries
  • Late arrival from a previous run

Disposable inboxes fix this by giving each run (or each attempt) an isolated inbox boundary.

Mailhook’s model aligns with this approach:

  • Create a disposable inbox via API
  • Use the returned email address for the security flow (signup verification, password reset)
  • Receive the email as structured JSON
  • Consume via webhook notifications (with signature verification) or polling
  • Expire or stop using the inbox when done

This is not only a reliability win, it is a security win: fewer opportunities for cross-run message mixups.

A simple reference architecture diagram showing an inbound email provider delivering a signed webhook to an ingestion service, then a normalization step producing JSON, an artifact extractor producing OTP/URL objects, and finally an LLM agent that can only call constrained tools like submit_otp or open_verified_link.

Logging and retention: keep security emails out of your prompts and out of your logs

Security emails often contain:

  • OTPs
  • session links
  • user identifiers
  • support links that can be abused

Operational recommendations:

  • Log message identifiers and correlation ids, not bodies.
  • If you store raw email for debugging, restrict access and set short retention.
  • Redact artifacts (OTPs, tokens) before writing logs.
  • Maintain a “model-visible” record separate from your full normalized record.

A clean separation is:

  • Raw (restricted, shortest retention you can tolerate)
  • Normalized JSON (internal only)
  • Minimized view (safe to show to LLMs)
  • Artifacts (OTP/URL), stored with consume-once semantics

A practical checklist for safe parsing of security emails

Use this as a pre-merge checklist when you add “read email” capabilities to an agent:

  • Webhook payloads are verified (signature over raw body), and replay is blocked.
  • The system can fall back to polling with timeouts and dedupe.
  • Parsing is JSON-first, and text/plain is preferred.
  • Artifact extraction is deterministic, and the LLM is not the first extractor.
  • The LLM receives a minimized view, not full HTML.
  • URL handling is allowlisted and SSRF hardened.
  • Side effects are artifact-idempotent (consume-once).
  • Logs and retention are explicitly designed for secrets.

Implementing this with Mailhook

If you want a provider that is designed for agent and QA workflows, Mailhook gives you building blocks that map directly to the controls above:

  • Programmable disposable inbox creation via API
  • Emails delivered as structured JSON
  • Real-time webhook notifications and a polling API
  • Signed payloads for webhook security
  • Custom domain support (useful when allowlisting and deliverability control matter)

For the exact API and semantics, use the canonical reference: Mailhook llms.txt. You can also start from the product overview at Mailhook and adapt the pipeline patterns in this article to your environment.

A developer-oriented illustration of a disposable inbox concept: an API creates short-lived inboxes, each with its own email address, feeding into isolated runs for signup verification and OTP extraction.

Related Articles