Data Flow

This page traces a form submission from the initial HTTP request through delivery to its final destination.

Ingestion Path

When a client submits a form, the following steps execute in the Cloudflare Worker:

  1. Parse the public key. The Worker extracts the publicKey from the URL path /v1/f/:publicKey.

  2. KV lookup. The Worker reads the form configuration from the FORM_KV namespace using the public key. If the key is not found or the form is inactive, it returns 404.

  3. Origin check. The Worker reads the Origin header and checks it against the form's allowedOrigins list. An empty list means all origins are allowed. A mismatch returns 403.

  4. Payload size check. If the Content-Length header exceeds 128 KB (131,072 bytes), the Worker returns 413.

  5. Parse JSON body. The Worker parses the request body as JSON. If parsing fails, it returns 400.

  6. Captcha verification (optional). If the form has verifyCaptcha enabled, the Worker reads the x-captcha-token header and verifies it against the captcha provider. A missing or invalid token returns 400.

  7. Build the SubmissionEnvelope. The Worker constructs the envelope containing the submission ID, form metadata, the parsed payload, and request metadata.

  8. Enqueue messages. For each active destination, the Worker enqueues a separate queue message containing the envelope and the destination config.

  9. Return 202 Accepted. The Worker responds immediately with the submission ID and the count of queued destinations, along with CORS headers if an origin was present.

HTTP/1.1 202 Accepted
Content-Type: application/json
Access-Control-Allow-Origin: https://example.com

{
  "ok": true,
  "submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "queuedDestinations": 2
}

SubmissionEnvelope

Every queue message wraps the form data in a SubmissionEnvelope:

type SubmissionEnvelope = {
  submissionId: string;
  formId: string;
  formName: string;
  payload: unknown;
  metadata: {
    origin: string | null;
    ip: string | null;
    userAgent: string | null;
    referer: string | null;
    submittedAt: string;
  };
};
Field Description
submissionId UUID generated per submission
formId The internal form ID from D1
formName Human-readable form name
payload The raw JSON body submitted by the client
metadata.origin The Origin header value
metadata.ip The client IP from CF-Connecting-IP
metadata.userAgent The User-Agent header value
metadata.referer The Referer header value
metadata.submittedAt ISO 8601 timestamp of when the submission was received

Queue Message

Each queue message pairs a single destination with the full envelope:

type QueueMessageBody = {
  submissionId: string;
  destination: {
    destinationId: string;
    type: 'smtp' | 'webhook' | 'google_sheets_webhook';
    config: Record<string, unknown>;
  };
  envelope: SubmissionEnvelope;
};

If a form has 3 destinations, 3 separate queue messages are produced from a single submission.

Queue Processing

The queue consumer processes messages in batches (max 10 per batch):

  1. Iterate messages. The consumer loops through each message in the batch.

  2. Dispatch to connector. Each message is passed to dispatchToDestination, which routes to the appropriate connector based on destination.type:

    • webhook -- sends an HTTP request to the configured URL
    • smtp -- sends an email via raw SMTP sockets
    • google_sheets_webhook -- sends an HTTP request to a Google Apps Script endpoint
  3. Handle result. The connector returns a DeliveryResult:

type DeliveryResult = {
  ok: boolean;
  responseCode?: number;
  responseBody?: string;
  error?: string;
};
  1. Acknowledge or retry.

    • If result.ok is true, the message is acknowledged (message.ack()).
    • If result.ok is false or an exception is thrown, the message is retried (message.retry()).
  2. Dead letter queue. After 5 failed retries, the message is moved to formdata-delivery-dlq. It will not be retried further.

Delivery Dispatch

The dispatchToDestination function routes based on destination type:

Type Connector What It Does
webhook sendWebhook HTTP POST/PUT/PATCH to the configured URL with the full envelope as the JSON body. Supports custom headers.
smtp sendSmtp Connects to the SMTP server via raw TCP sockets (cloudflare:sockets). Supports STARTTLS, plain TLS, and unencrypted connections. Sends a formatted email with the submission data.
google_sheets_webhook sendGoogleSheetsWebhook HTTP POST/PUT to a Google Apps Script web app URL. Optionally includes an authorization header.

End-to-End Example

Here is the complete flow for a form with a webhook and an email destination:

  1. Client sends POST /v1/f/pk_abc123 with a JSON body.
  2. Worker looks up pk_abc123 in KV, finds an active form with 2 destinations.
  3. Worker validates origin and payload, builds the SubmissionEnvelope.
  4. Worker enqueues 2 messages: one for the webhook, one for SMTP.
  5. Worker returns 202 Accepted with queuedDestinations: 2.
  6. Queue consumer picks up the webhook message, sends HTTP POST to the configured URL, gets 200 OK, and acknowledges.
  7. Queue consumer picks up the SMTP message, connects to the mail server, sends the email, and acknowledges.
  8. Both deliveries are complete. No submission data is stored.
INFO

The 202 response means the submission was accepted and queued for delivery. It does not guarantee delivery succeeded. If a destination is down, the queue retries up to 5 times with a 30-second delay between attempts.