Skip to content
Inbound email

Inbound email parsing: receive, thread, and reply

Build an inbound email path that verifies signed webhooks, reads the parsed reply in its conversation, and sends a threaded response.

Published

Inbound email matters when a reply changes what your product does next. A support reply can update a ticket, a customer response can unblock an approval, and a forwarded message can become an application event. The useful integration is a small, verified path from a received email to the conversation your application already understands.

This page is the minimum working path with the TypeScript SDK. The complete, runnable recipe lives in the inbound email cookbook, including the full example, durable deduplication, and production hardening.

Configure receiving

Start with a verified domain and a public HTTPS endpoint. Creating the endpoint returns a signing secret; enabling receiving with that endpoint ID creates the receipt rule and reports its rule name and recipient addresses. Publish the root MX record Samva proposes before mail can route, and use a dedicated subdomain when an existing mailbox owns your primary MX route.

import { createClient } from "samva";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

const endpoint = await samva.webhooks.create({
  name: "Inbound replies",
  url: "https://app.example.com/webhooks/samva",
  eventTypes: ["message.received"],
  channels: ["email"],
});

await samva.email.enableReceiving({
  id: process.env.SAMVA_DOMAIN_ID!,
  domain: "replies.example.com",
  catchAll: true,
  endpointId: endpoint.endpoint.id,
});

Endpoint names are unique per organization. If enabling receiving fails after you create the endpoint, list endpoints and reconcile the existing one with webhooks.update instead of creating a second. A signing secret is shown only once; rotate it with webhooks.regenerateSecret if the first response was lost.

Verify, read the thread, and reply

message.received carries parsed fields including from, to, subject, conversationId, hasAttachments, and isAutoReply, plus a parsed body when available. Verify the raw request before parsing JSON, then read the conversation and reply on the same thread.

import { createClient } from "samva";
import { verifyRequest } from "samva/webhooks";

const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });

export async function POST(request: Request) {
  const verified = await verifyRequest(request, process.env.SAMVA_WEBHOOK_SECRET!);
  if (verified.event.type !== "message.received") return new Response(null, { status: 204 });

  const reply = verified.event.data as {
    messageId: string;
    conversationId: string;
    from: string;
    subject?: string;
    isAutoReply?: boolean;
  };

  if (reply.isAutoReply) return new Response(null, { status: 204 });

  const conversation = await samva.conversations.getById({ id: reply.conversationId });

  await samva.email.send(
    {
      conversationId: conversation.id,
      to: reply.from,
      subject: reply.subject ? `Re: ${reply.subject}` : "Re: your message",
      html: "<p>Thanks for your reply. We will follow up shortly.</p>",
      inReplyToMessageId: reply.messageId,
    },
    { headers: { "idempotency-key": verified.id } },
  );

  return new Response(null, { status: 204 });
}

Filter isAutoReply so an out-of-office response never triggers another reply. Deduplicate on webhook-id, the event id that is stable across retries, and pass it as the idempotency-key header so a concurrent retry cannot double-send while a transient failure stays retryable. Keep conversationId and inReplyToMessageId so your application and the recipient see one continuous conversation.

The cookbook covers the durable deduplication store, MX and subdomain handling, and the runnable example.

For the event schema, see the webhook event catalog. For endpoint verification and retry behavior, see receive webhooks.

Frequently Asked Questions

Related Resources

Send

Ship your first email today.

Transactional and product email through one typed API, with signed events, conversation threading, and deliverability handled.