← Back to documentation

Setting Up a Form with PayloadRelay as the Backend

Create contact forms, feedback widgets, and signup forms that deliver submissions via email, Slack, or any configured target — no backend code required.

10 min read

Use PayloadRelay as the backend for any HTML form. Submissions are forwarded to your configured targets (email, Slack, webhook, etc.) with zero server-side code.

Purpose

This example helps you:

  • Build an HTML form that posts directly to a PayloadRelay endpoint.
  • Handle form-encoded and JSON submissions.
  • Configure an email target to receive form entries.
  • Set up CORS so browser-based submissions succeed.
  • Style the form and add client-side validation.

Prerequisites and permissions

  • A PayloadRelay account with an active endpoint.
  • At least one confirmed relay target (email recommended for contact forms).
  • The endpoint must accept POST with Form or JSON payload format.
  • CORS origins configured on the endpoint if submitting from a browser.

Step-by-step workflow

1. Create the endpoint

  1. Open Endpoints and select Create endpoint.
  2. Set the accepted method to POST.
  3. Set payload format to Form (or JSON if you plan to submit with JavaScript).
  4. In Security, add your site domain to Allowed CORS origins (e.g. https://example.com).
  5. In Target destinations, attach a confirmed email target.
  6. Save and copy the endpoint URL.

2. Basic HTML form (form-encoded)

Code Example
<form
  action="https://api.payloadrelay.com/relay/YOUR_ENDPOINT_ID"
  method="POST"
>
  <label for="name">Name</label>
  <input type="text" id="name" name="name" required />

  <label for="email">Email</label>
  <input type="email" id="email" name="email" required />

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="5" required></textarea>

  <button type="submit">Send</button>
</form>

When submitted, the browser sends a application/x-www-form-urlencoded POST. PayloadRelay parses the fields and delivers them to your email target.

3. JSON submission with JavaScript

For more control over the submission flow (loading states, error handling, staying on the same page), submit with fetch():

Code Example
<form id="contact-form">
  <label for="name">Name</label>
  <input type="text" id="name" name="name" required />

  <label for="email">Email</label>
  <input type="email" id="email" name="email" required />

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="5" required></textarea>

  <button type="submit">Send</button>
  <p id="status"></p>
</form>

<script>
  const form = document.getElementById("contact-form");
  const status = document.getElementById("status");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();
    status.textContent = "Sending…";

    const data = Object.fromEntries(new FormData(form));

    try {
      const res = await fetch(
        "https://api.payloadrelay.com/relay/YOUR_ENDPOINT_ID",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(data),
        }
      );

      if (res.ok) {
        status.textContent = "Sent! We'll be in touch.";
        form.reset();
      } else {
        status.textContent = "Something went wrong. Please try again.";
      }
    } catch {
      status.textContent = "Network error. Please try again.";
    }
  });
</script>

When using JSON submission, set the endpoint payload format to JSON.

4. CORS configuration

Browser-based form submissions require CORS. PayloadRelay supports configurable CORS origins on each endpoint.

  1. Open the endpoint in Endpoints.
  2. Under Security, find Allowed CORS origins.
  3. Add each domain that will host your form:
    • https://example.com
    • https://www.example.com
    • http://localhost:3000 (for local development)

Without CORS configuration, browser fetch() calls will fail with a CORS error. Native <form> submissions (without JavaScript) do not require CORS but will navigate the browser away from your page.

5. Styled contact form example

Code Example
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Contact Us</title>
    <style>
      * { box-sizing: border-box; margin: 0; padding: 0; }
      body { font-family: system-ui, sans-serif; background: #f5f5f5; padding: 2rem; }
      .form-card {
        max-width: 480px; margin: 0 auto; background: #fff;
        border-radius: 8px; padding: 2rem; box-shadow: 0 2px 8px rgba(0,0,0,0.1);
      }
      h1 { font-size: 1.5rem; margin-bottom: 1.5rem; }
      label { display: block; font-weight: 600; margin-bottom: 0.25rem; font-size: 0.9rem; }
      input, textarea {
        width: 100%; padding: 0.5rem; border: 1px solid #ccc;
        border-radius: 4px; margin-bottom: 1rem; font-size: 1rem;
      }
      textarea { resize: vertical; }
      button {
        background: #2563eb; color: #fff; border: none; padding: 0.75rem 1.5rem;
        border-radius: 4px; font-size: 1rem; cursor: pointer; width: 100%;
      }
      button:hover { background: #1d4ed8; }
      #status { margin-top: 1rem; text-align: center; font-size: 0.9rem; }
    </style>
  </head>
  <body>
    <div class="form-card">
      <h1>Contact Us</h1>
      <form id="contact-form">
        <label for="name">Name</label>
        <input type="text" id="name" name="name" required />

        <label for="email">Email</label>
        <input type="email" id="email" name="email" required />

        <label for="subject">Subject</label>
        <input type="text" id="subject" name="subject" />

        <label for="message">Message</label>
        <textarea id="message" name="message" rows="5" required></textarea>

        <button type="submit">Send Message</button>
        <p id="status"></p>
      </form>
    </div>

    <script>
      const form = document.getElementById("contact-form");
      const status = document.getElementById("status");
      const btn = form.querySelector("button");

      form.addEventListener("submit", async (e) => {
        e.preventDefault();
        btn.disabled = true;
        status.textContent = "Sending…";

        const data = Object.fromEntries(new FormData(form));

        try {
          const res = await fetch(
            "https://api.payloadrelay.com/relay/YOUR_ENDPOINT_ID",
            {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify(data),
            }
          );

          if (res.ok) {
            status.textContent = "✓ Message sent successfully!";
            status.style.color = "#16a34a";
            form.reset();
          } else {
            status.textContent = "⚠ Failed to send. Please try again.";
            status.style.color = "#dc2626";
          }
        } catch {
          status.textContent = "⚠ Network error. Please try again.";
          status.style.color = "#dc2626";
        } finally {
          btn.disabled = false;
        }
      });
    </script>
  </body>
</html>

6. Configure the email target

When an email target receives a form submission, the payload fields are formatted in the email body. To set this up:

  1. Open Relay targets and select Add target.
  2. Choose Email and enter the address where submissions should arrive.
  3. Confirm the email target via the confirmation link.
  4. Attach the target to your form endpoint under Target destinations.

Each form submission generates an email containing the submitted field names and values.

Expected result and verification checks

  • Form submissions appear in Request activity with outcome ACCEPTED.
  • Email target receives formatted field data within seconds.
  • Browser console shows no CORS errors.
  • The form resets and shows a success message after submission.

Common issues and fixes

  • CORS error in browser console: add the exact origin (including protocol and port) to the endpoint's Allowed CORS origins.
  • METHOD_NOT_ALLOWED: verify the endpoint accepts POST.
  • PAYLOAD_TOO_LARGE: form payloads must stay within plan limits.
  • No email received: confirm the email target status is Confirmed and the target is attached as an endpoint output.
  • FIELD_VALIDATION_FAILED: check that the payload format matches what the endpoint expects (Form vs JSON).

Related guides