ThisCalculator

Everyday

Save Email to D1 With a Cloudflare Email Worker

Updated · Standard formula, shown below

Save Email to D1 With a Cloudflare Email Worker

Yes. A Cloudflare Email Worker can receive an email and save its raw message to a D1 database with roughly 30 lines of JavaScript, provided you create the database, bind it to the Worker, and create a table first. The example below stores the sender, recipient, timestamp, and full message; it also explains an important limitation: D1 is suited to modest message storage, not automatically a replacement for an email archive or object store.

How the email-to-D1 flow works

Cloudflare Email Routing can direct mail for a configured address or domain to an Email Worker. The Worker receives an email event, whose message object exposes envelope information and the raw message as a readable stream. It can read that stream into bytes, convert those bytes to text, and issue a parameterized SQL insert through a D1 binding.

The route and the Worker are separate pieces of setup. First enable Email Routing for a domain and create a routing rule that sends the chosen address to the Worker. Then deploy the Worker and attach a D1 database binding. The route determines which messages arrive; the Worker determines what happens to them.

Here is a compact example. It assumes a D1 database binding named DB and a table named inbound_email with columns matching the insert:

export default {
  async email(message, env, ctx) {
    const raw = new TextDecoder().decode(
      await new Response(message.raw).arrayBuffer()
    );
    await env.DB.prepare(
      "INSERT INTO inbound_email (received_at, from_addr, to_addr, raw) VALUES (?, ?, ?, ?)"
    ).bind(
      new Date().toISOString(), message.from, message.to, raw
    ).run();
  }
};

This is intentionally a minimal handler, not a complete production mail-processing system. The Worker’s Email handler is named email; it receives the message and environment bindings. The ctx argument is included for the standard handler signature but is not needed in this example.

Create the D1 table and binding

Create a database using the Cloudflare dashboard or Wrangler, then create a table in it. For example, run this SQL against the database:

CREATE TABLE inbound_email (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  received_at TEXT NOT NULL,
  from_addr TEXT NOT NULL,
  to_addr TEXT NOT NULL,
  raw TEXT NOT NULL
);

In the Worker configuration, bind that database using the name DB. The binding name is what makes it available as env.DB; if you choose another name, update the code accordingly. Configure the database ID in the Worker’s D1 binding settings, or in the Wrangler configuration, and deploy the Worker. A successful deployment alone does not create the table: schema setup is a separate step.

Test the schema and binding before directing important mail to the Worker. A misspelled table or column name, a missing binding, or a database created in a different environment can cause the insert to fail. Keep development and production databases clearly separated so a test message does not become a production record.

What exactly gets saved?

The example stores the raw email content, including its headers and MIME body, as text, alongside the envelope sender and recipient supplied by the event. Raw email is not the same thing as a clean, human-readable body. Messages can contain multipart alternatives, attachments, encoded text, and nested MIME sections. The raw field preserves the message representation, but it does not extract attachment files or turn HTML into plain text.

Converting the stream to a string is convenient, but email is fundamentally byte-oriented. If exact byte-for-byte preservation matters, consider storing a binary representation rather than relying on text decoding; a text conversion can be unsuitable for arbitrary binary content. A common design is to store message bytes or attachments in object storage and keep a database row with metadata and a storage key. That avoids placing large message payloads directly in a relational row.

Also consider whether the envelope addresses are sufficient for your use. The visible From: header can differ from the SMTP envelope sender. If your application needs parsed headers, message IDs, or attachment metadata, parse the raw message with an appropriate MIME parser and decide explicitly which fields to retain.

Limits, reliability, and common failures

D1 is a serverless SQL database, but that does not mean every message size, write rate, or retention pattern is a good fit. Email bodies and attachments can be large, and storing every raw message increases database size and query cost. Check current Cloudflare limits for D1, Workers, and Email Routing before relying on a particular maximum message size or throughput; service limits and plan details can change.

  • Stream or size errors: reading all of message.raw into memory uses memory proportional to the message size. Large messages may make this approach unsuitable.
  • Database errors: verify the DB binding, database environment, table name, and column names. Inspect Worker logs for failed inserts.
  • Duplicate delivery or retries: design for the possibility that processing may be retried. If duplicate rows would be harmful, retain a stable message identifier when available and enforce uniqueness, while deciding how to handle messages without one.
  • Character encoding: text conversion is convenient for ordinary message data but should not be mistaken for lossless binary archival.

Do not silently discard failures. Log an error without exposing message bodies or sensitive headers, and decide whether the Worker should reject the message or route it to a fallback destination. For important mail, test the actual failure behavior and keep a recovery path rather than assuming a successful route guarantees a durable database write.

Privacy and storage choices

Email often contains personal, confidential, or business-sensitive information. Store only what the application needs, restrict who can query the database, avoid printing full messages to logs, and define a deletion and retention policy. Database access controls and operational practices matter even when the Worker code is short.

For small messages or searchable metadata, D1 can be a straightforward choice. For large raw messages, attachments, or long-term archival, object storage is often a better place for the payload, with D1 holding searchable metadata and a reference. Another option is forwarding to an existing mail system if the goal is simply to receive and read mail rather than build an application around it. The right choice depends on whether you need SQL queries, attachment handling, retention, or a conventional mailbox.

Frequently Asked Questions

Can a Cloudflare Email Worker write directly to D1?
Yes. Attach a D1 database binding to the Worker and use the binding in the Email handler to run a prepared SQL statement. The database and table must exist first.

Does this save attachments as files?
No. Saving the raw message preserves its MIME content as a message representation, but it does not extract attachments into separate files. Parse MIME parts or store payloads separately if you need individual attachments.

Why does the code use a prepared statement?
Binding values with prepare and bind keeps message data separate from SQL syntax. Do not build SQL by concatenating sender addresses or message content into the query.

Can D1 hold the entire raw email?
It can store text data, but whether it is appropriate depends on message size, database limits, and workload. Large messages and attachments are often better stored in object storage, with metadata in D1.

Do I need Email Routing as well as a Worker?
You need an email-delivery path that invokes the Worker. With Cloudflare Email Routing, configure a route to the Worker; creating a Worker by itself does not make an address receive mail.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.