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.
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
POSTwithFormorJSONpayload format. - CORS origins configured on the endpoint if submitting from a browser.
Step-by-step workflow
1. Create the endpoint
- Open
Endpointsand selectCreate endpoint. - Set the accepted method to
POST. - Set payload format to
Form(orJSONif you plan to submit with JavaScript). - In
Security, add your site domain toAllowed CORS origins(e.g.https://example.com). - In
Target destinations, attach a confirmed email target. - Save and copy the endpoint URL.
2. Basic HTML form (form-encoded)
<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():
<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.
- Open the endpoint in
Endpoints. - Under
Security, findAllowed CORS origins. - Add each domain that will host your form:
https://example.comhttps://www.example.comhttp://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
<!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:
- Open
Relay targetsand selectAdd target. - Choose
Emailand enter the address where submissions should arrive. - Confirm the email target via the confirmation link.
- 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 activitywith outcomeACCEPTED. - 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 acceptsPOST.PAYLOAD_TOO_LARGE: form payloads must stay within plan limits.- No email received: confirm the email target status is
Confirmedand the target is attached as an endpoint output. FIELD_VALIDATION_FAILED: check that the payload format matches what the endpoint expects (FormvsJSON).