// use cases

Quarantine hostile email before agents read it

[ view markdown ]

Put Context Guardrails between Resend and your agent so phishing, prompt injection, and malicious attachments never enter agent context.

Treat every inbound email as untrusted until it clears Context Guardrails. Resend receives the message, your webhook fetches the raw email, and Superagent checks the sender, body, attachments, and links before your application passes anything to an agent.

Use this verdict policy:

  • safe — deliver to the agent
  • caution — hold for review
  • suspicious or dangerous — quarantine

Prerequisites

  • A Resend account with a receiving domain
  • A Next.js application with a public webhook route
  • RESEND_API_KEY, RESEND_WEBHOOK_SECRET, and SUPERAGENT_API_KEY stored in the application environment
  • An existing queue or function that delivers approved email to your agent

1. Receive email with Resend

Open the Receiving tab in Resend and copy the account's .resend.app address, or configure a custom domain with its required MX record.

Create a webhook in Resend:

  1. Open Webhooks and select Add Webhook.
  2. Enter your application's HTTPS route, such as https://app.example.com/api/webhooks/resend.
  3. Subscribe to email.received.
  4. Copy the webhook signing secret into RESEND_WEBHOOK_SECRET.

The email.received payload contains message metadata and an email_id. Fetch the stored raw message before scanning so Context Guardrails can inspect the original headers, MIME structure, attachments, HTML, text, and links.

2. Verify and scan the raw email

Create app/api/webhooks/resend/route.ts:

import { Resend } from "resend";
import { NextResponse } from "next/server";

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(request: Request) {
  const payload = await request.text();

  let event;
  try {
    event = resend.webhooks.verify({
      payload,
      headers: {
        id: request.headers.get("svix-id") ?? "",
        timestamp: request.headers.get("svix-timestamp") ?? "",
        signature: request.headers.get("svix-signature") ?? "",
      },
      webhookSecret: process.env.RESEND_WEBHOOK_SECRET!,
    });
  } catch {
    return new Response("Invalid webhook signature", { status: 400 });
  }

  if (event.type !== "email.received") {
    return NextResponse.json({ status: "ignored" });
  }

  const { data: email, error } = await resend.emails.receiving.get(
    event.data.email_id,
  );
  const downloadUrl = email?.raw?.download_url;
  if (error || !downloadUrl) {
    return NextResponse.json(
      { error: "Raw email is unavailable" },
      { status: 502 },
    );
  }

  const rawResponse = await fetch(downloadUrl);
  if (!rawResponse.ok) {
    return NextResponse.json(
      { error: "Raw email download failed" },
      { status: 502 },
    );
  }
  const rawEmail = await rawResponse.arrayBuffer();

  const scanResponse = await fetch(
    "https://superagent.sh/api/v1/context/email?mode=full&details=true",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SUPERAGENT_API_KEY}`,
        "Content-Type": "message/rfc822",
      },
      body: rawEmail,
      signal: AbortSignal.timeout(90_000),
    },
  );
  if (!scanResponse.ok) {
    return NextResponse.json(
      { error: "Email guardrail scan failed" },
      { status: 502 },
    );
  }

  const artifact = (await scanResponse.json()).data;

  if (
    artifact.verdict === "suspicious" ||
    artifact.verdict === "dangerous"
  ) {
    return NextResponse.json({
      status: "quarantined",
      identifier: artifact.identifier,
      verdict: artifact.verdict,
    });
  }

  if (artifact.verdict === "caution") {
    return NextResponse.json({
      status: "review",
      identifier: artifact.identifier,
    });
  }

  // This is the only branch that may enqueue rawEmail for your agent.
  return NextResponse.json({
    status: "approved",
    emailId: event.data.email_id,
    identifier: artifact.identifier,
  });
}

Webhook verification must use request.text() before JSON parsing. Resend signatures are calculated from the untouched body, so parsing and serializing the payload before verification invalidates the signature.

mode=full waits for the completed scan. If the receiving platform cannot wait up to 90 seconds, persist the verified email_id, enqueue processing, return 202, and run the raw-email fetch and scan in a worker.

3. Deliver only approved email to the agent

Replace the final approved response with the application's existing queue or agent handoff. Pass rawEmail only from that branch.

Keep the other outcomes separate:

  • Send caution to a review queue.
  • Store the scan identifier and verdict for quarantined messages.
  • Return 2xx for reviewed and quarantined messages so Resend does not retry content you intentionally withheld.
  • Return a retryable error only when signature verification succeeded but the raw email or guardrail service was temporarily unavailable.

The agent should never summarize, reply to, follow links from, or call tools with an email before the safe verdict.

4. Test the complete email boundary

  1. Send a Resend webhook test and confirm the signature verifies.
  2. Send a normal email and confirm it returns approved.
  3. Send a test message containing an instruction aimed at the agent and confirm it returns suspicious or dangerous.
  4. Confirm the unsafe message never enters the agent queue, logs, or prompt.
  5. Inspect the Context Guardrails result for sender authentication, MIME, attachment, and outbound-link evidence.

Next steps