---
title: "Email to Database | WebhookRelay"
meta:
  "og:description": "Store every inbound email in your own database. Webhook Relay parses email to JSON and POSTs it to a tiny insert endpoint you run — even one behind a firewall."
  "og:title": "Email to Database"
  description: "Store every inbound email in your own database. Webhook Relay parses email to JSON and POSTs it to a tiny insert endpoint you run — even one behind a firewall."
---

![Stripes](https://webhookrelay.com/docs/tutorials/email/database/images/stripes.svg)

Documentation

**Fundamentals**

# **Email to Database**

Store every inbound email in your own database. Webhook Relay parses email to JSON and POSTs it to a tiny insert endpoint you run — even one behind a firewall.

You want every inbound email stored in **your own database** — an archive, an audit trail, or a queue your app reads from. Doing that the traditional way means running a mail server or polling an IMAP mailbox, parsing raw MIME, and writing a daemon to keep it alive.

[Webhook Relay](https://webhookrelay.com) removes all of that. It [receives emails as webhooks](https://webhookrelay.com/docs/tutorials/email/database/docs/email/): you get a unique address, every message sent to it is parsed into JSON, and that JSON is POSTed to a tiny HTTP endpoint you run — which does a single `INSERT`. No mail server, no IMAP loop.

## [How it works](#how-it-works)

Webhook Relay doesn't connect to your database directly. It parses the email and POSTs it to an HTTP handler you control, and that handler owns the SQL:

```
inbound email  ──▶  Webhook Relay (parse)  ──▶  POST  ──▶  your insert endpoint  ──▶  database
```

The parsed email looks like this:

```
{
  "from": "jon@example.com",
  "from_name": "Jon Snow",
  "recipient": "<uuid>@in.webhookrelay-mail.com",
  "to": ["<uuid>@in.webhookrelay-mail.com"],
  "subject": "Invoice #4821",
  "date": "Sun, 28 Jun 2026 11:27:41 +0400",
  "message_id": "<abc@mail.example.com>",
  "text": "plain body",
  "html": "<p>html body</p>",
  "spf": "pass",
  "dkim": "pass",
  "dmarc": "pass",
  "attachments": [
    { "name": "invoice.pdf", "content_type": "application/pdf", "size": 48213, "content": "<base64>", "truncated": false }
  ]
}
```

See the full [payload reference](https://webhookrelay.com/docs/tutorials/email/database/docs/email/payload/) for every field, including `cc` and the raw `headers` map.

## [Setup](#setup)

**1. Create a bucket.** This ties an input to its outputs.

**2. Add an email input.** Webhook Relay returns a unique address of the form `<uuid>@in.webhookrelay-mail.com`. Anything sent there is parsed and forwarded to the bucket's outputs.

**3. Run your insert endpoint.** A small HTTP service that accepts a POST and writes one row (see below).

**4. Add its URL as the output.** Point the bucket's output at your endpoint, e.g. `https://api.example.com/inbound`.

**5. (Optional) attach a transform** to trim the payload to just the columns you store.

**6. Send a test email.** Email your `<uuid>@in.webhookrelay-mail.com` address. Within a second or two a row appears. If it doesn't, open the request log in your dashboard to see exactly what was received and delivered.

## [The handler](#the-handler)

The endpoint receives the email JSON and inserts it. Use `message_id` with a `UNIQUE` constraint so the write is idempotent:

```
app.post('/inbound', async (req, res) => {
  const e = req.body; // the parsed email JSON
  await db.query(
    'INSERT INTO emails(message_id, sender, subject, body, received_at) VALUES($1,$2,$3,$4,$5) ON CONFLICT (message_id) DO NOTHING',
    [e.message_id, e.from, e.subject, e.text, e.date]
  );
  res.sendStatus(200);
});
```

That `ON CONFLICT (message_id) DO NOTHING` is what makes retries safe. Webhook Relay [durable retries](https://webhookrelay.com/docs/tutorials/email/database/docs/webhooks/durable-webhooks/) re-attempt any delivery your endpoint didn't acknowledge with a `2xx`, so an email is never lost if your service is briefly down or slow. Because each email carries a stable `message_id`, a retried delivery hits the unique constraint and becomes a no-op instead of a duplicate row.

## [Trim the payload (optional)](#trim-the-payload-optional)

If you only store a handful of columns, a [transform function](https://webhookrelay.com/docs/tutorials/email/database/docs/webhooks/functions/) can shrink the body before it ever reaches your endpoint — your handler then deals with a small, predictable shape:

```
const e = JSON.parse(r.body);
r.setBody(JSON.stringify({
  message_id: e.message_id,
  from: e.from,
  subject: e.subject,
  body: e.text,
  received_at: e.date
}));
```

This keeps base64 attachment blobs and HTML bodies out of the request when you don't need them.

## [Delivering to a private database](#delivering-to-a-private-database)

Your endpoint usually sits next to the database, on a private network. You don't have to expose it. Run the [relay agent](https://webhookrelay.com/docs/tutorials/email/database/webhooks/) alongside the service and Webhook Relay forwards each email to it over an outbound connection — so you can [deliver behind a firewall](https://webhookrelay.com/docs/tutorials/email/database/webhooks/) or to `localhost` with no public IP and no inbound ports open. Durable retries still apply, so a host that's briefly unreachable catches up automatically.

## [Get started](#get-started)

Storing inbound mail in a database is one case of a broader pattern: [Email to Webhook](https://webhookrelay.com/docs/tutorials/email/database/email-to-webhook/). Once an email is JSON, you can route it to any endpoint a webhook can reach — including a one-line insert handler.

[Create a free Webhook Relay account](https://my.webhookrelay.com/register), add an email input, point it at your endpoint, and start archiving every inbound message in minutes.

Did this page help you?