Skip to content

Developer guide · Signup forms

Build your own signup experience.

Keep your own HTML, styling, and website framework. Connect your custom form to a published smartest.email form through its public signup endpoint.

This guide covers

Public form configuration and signups from any website origin

Consent, validation, responses, and rate limits

Use the URL from your form.

Create a form in Signup forms, provide a required email field and a required consent checkbox, then publish it. The Install tab shows the exact URL for your form.

Base URL

https://api.smartest.email/api/v1/public/forms/{FORM_ID}

Use the URL shown on your form’s Install tab. Replace FORM_ID with your published form’s ID in the examples below.

1

Create

Set your fields and a clear consent label.

2

Publish

New forms start as drafts; public routes return 404 until published.

3

Integrate

Use the embed snippet or call the two public endpoints below.

Use your own HTML form.

Direct submission is supported. Create and publish a form in smartest.email to define its fields and consent wording, then use its form ID in your existing website form. You can choose the embedded form type without installing the widget.

Your website can submit with JavaScript fetch(), or your own server can forward the signup as JSON. The public endpoint needs no API key, access token, or smartest.email embed script.

POST
https://api.smartest.email/api/v1/public/forms/{FORM_ID}/submit

Replace FORM_ID with the UUID of your published form. Send Content-Type: application/json with the payload below.

JSON request · after the visitor gives consent
{
  "data": {
    "email": "visitor@example.com"
  },
  "consent": true
}

Set consent from the visitor’s explicit choice, and display the form’s consent wording and privacy notice. Keep consent at the top level; place configured contact fields inside data. Include every required field in your published form.

A plain HTML form action needs an adapter

The endpoint accepts JSON only. A normal <form method="post" action="…"> sends URL-encoded or multipart data and receives 415 JSON body required. Use the browser JavaScript example below, or post to your own server and have it send JSON to smartest.email.

Fetching the public configuration keeps your labels and validation aligned with the published form. It is not a prerequisite for each submission. Your integration handles its own success message or redirect; this endpoint always responds with JSON.

Render the fields you published.

This public endpoint needs no access token. Fetch it when displaying a form, then use the returned field names, consent label, and privacy URL in your custom UI.

GET
https://api.smartest.email/api/v1/public/forms/{FORM_ID}

Returns 200 with safe rendering data for an active form. Every successful fetch increments its view count; avoid polling.

GET
https://api.smartest.email/api/v1/public/forms/{FORM_ID}/embed.js

Ready-made inline or popup widget script. Use the exact script URL from Install if you do not need a custom UI.

GET
https://api.smartest.email/api/v1/public/forms/{FORM_ID}/landing

Hosted signup page for an active form.

GET /api/v1/public/forms/{FORM_ID} · example response
{
  "id": "FORM_ID",
  "name": "Newsletter",
  "organizationName": "Example Studio",
  "type": "embedded",
  "fields": [
    { "name": "email", "type": "email", "label": "Email address", "required": true },
    { "name": "consent", "type": "checkbox", "label": "I agree to receive email updates.", "required": true }
  ],
  "styling": {},
  "successAction": {},
  "privacyUrl": "https://example.com/privacy"
}

The response can include additional configured fields, choices, and validation rules. successAction describes behaviour for the ready-made widget; direct submission still returns the generic JSON response below. The script and hosted page return 404 for draft, unpublished, invalid, or unknown forms.

Send consent with the data.

Post JSON after the visitor explicitly chooses to receive marketing email. The consent flag belongs at the top level; do not put consent inside data.

POST
https://api.smartest.email/api/v1/public/forms/{FORM_ID}/submit

No access token. Send Content-Type: application/json and {data:{email:...},consent:true}. Returns the same generic success for new and existing addresses.

HTML + browser JavaScript · email and consent form
<form id="signup-form">
  <fieldset id="signup-fields" disabled>
    <legend>Subscribe to email updates</legend>
    <label id="email-label" for="signup-email">Email address</label>
    <input id="signup-email" type="email" autocomplete="email" required>
    <label for="signup-consent">
      <input id="signup-consent" type="checkbox" required>
      <span id="consent-label"></span>
    </label>
    <a id="privacy-link" href="/privacy">Privacy notice</a>
    <button id="signup-submit" type="submit">Subscribe</button>
  </fieldset>
  <p id="signup-status" role="status" aria-live="polite"></p>
</form>
<script type="module">
  const endpoint = "https://api.smartest.email/api/v1/public/forms/FORM_ID";
  const form = document.querySelector("#signup-form");
  const fields = document.querySelector("#signup-fields");
  const button = document.querySelector("#signup-submit");
  const status = document.querySelector("#signup-status");
  let ready = false;
  let submitting = false;

  form.addEventListener("submit", async event => {
    event.preventDefault();
    if (!ready || submitting || !form.reportValidity()) return;
    submitting = true;
    button.disabled = true;
    try {
      const response = await fetch(endpoint + "/submit", {
        method: "POST",
        credentials: "omit",
        headers: { "Content-Type": "application/json", "Accept": "application/json" },
        body: JSON.stringify({
          data: { email: document.querySelector("#signup-email").value.trim() },
          consent: document.querySelector("#signup-consent").checked
        })
      });
      const result = await response.json();
      status.textContent = result.message || "Please try again.";
      if (response.ok) form.reset();
    } catch {
      status.textContent = "Please try again.";
    } finally {
      submitting = false;
      button.disabled = false;
    }
  });

  try {
    const response = await fetch(endpoint, { credentials: "omit" });
    if (!response.ok) throw new Error("Signup form unavailable");
    const config = await response.json();
    document.querySelector("#email-label").textContent =
      config.fields.find(field => field.name === "email").label;
    document.querySelector("#consent-label").textContent =
      config.fields.find(field => field.name === "consent").label;
    if (config.privacyUrl) {
      document.querySelector("#privacy-link").href = config.privacyUrl;
    }
    ready = true;
    fields.disabled = false;
  } catch (error) {
    status.textContent = error.message;
  }
</script>

Replace FORM_ID with your form’s UUID, and set the fallback /privacy link to your own notice if the form has no configured privacyUrl. This example handles the two required fields; render and send any additional required fields from your published configuration.

Accepted response

JSON
{"success":true,"message":"Thank you for subscribing."}

The response intentionally does not say whether an address already exists. Existing subscribed and unsubscribed contacts keep their current state; only a new accepted address adds a subscriber and consent record.

Only send names present in the published fields array. Required fields must be supplied. For extra fields, send checkbox values as booleans, number values as numeric strings, dates as YYYY-MM-DD, and select or radio values exactly as listed in options. The API validates the data again on the server.

The optional top-level website property is a spam honeypot. Omit it or leave it empty for a normal submission. A nonempty value returns the generic success response without adding a contact; it is not a field for the visitor’s website URL.

Handle failures and traffic limits.

Public form endpoints allow cross-origin GET, POST, and OPTIONS requests from external websites with Content-Type and Accept headers. Use credentials: omit in browser requests; no account cookies, Authorization header, or API key is needed.

StatusMeaning
200Configuration or accepted submit; a duplicate address gets the same submit response.
400 / 415Malformed JSON / request is not application/json.
404Unknown, draft, unpublished, or invalid form.
409The account has reached its contact limit.
413 / 422Body exceeds 16 KiB / consent or field data is invalid.
429Rate limit reached; browser code can use retry_after in the JSON body. Server integrations can also read Retry-After.

Errors use {"error":true,"message":"..."}; rate-limit errors also include retry_after in seconds. Form requests allow up to 30 requests per IP per minute, including up to 10 submissions. Your website’s Content Security Policy must allow https://api.smartest.email in connect-src; the ready-made embed also needs it in script-src.

When your own server forwards submissions, they share that server’s outgoing IP for rate limiting.

Consent and delivery

A new accepted signup creates a subscribed contact and saves the form’s consent label, time, and supplied request origin. Your custom UI must display the form’s consent text and privacy notice before the visitor submits. The recorded request origin is not proof of identity. This endpoint uses single opt-in; it does not send a confirmation email or verify ownership of the address.