Guides

Collecting signups

Waitlists and newsletters from any website: a link, one line, your own HTML or code.

A signup form lets people join a waitlist or a newsletter from any website, without you building a backend. You make the form in the dashboard (Marketing → Signup forms), choose the list people join, and put it on your site in one of four ways. Everyone who joins lands on that list, and any automation that starts "when someone joins the list" sends your welcome email.

This guide is for the developer putting the form on a site. The dashboard's Share tab shows every snippet below with your form's real id already in it.

The form id

Every form has a public id like frm_8kq2xv9btn4lw0. It is not a secret: knowing it lets you sign up, which is the point. It cannot read anything, and it only ever adds to the lists the form names.

Nothing below needs an API key. A form only takes signups while it is Live.

https://mailyte.com/f/<form id> is a page we host with your logo, words and colour. Put it in a post, a bio, an email or a QR code. It is not indexed by search engines.

On your own address

On Growth plans and up, the page can live on your own subdomain, like join.acme.com. On the form's Share tab, under Your own address, enter the subdomain; it must sit on a domain you have verified on the Domains page. Then add one DNS record:

Type Name Value
CNAME join forms.mailyte.com

On Cloudflare, set it to DNS only (grey cloud). We look for it every ten minutes, then set up the secure certificate. The Share tab shows each step, and the page goes live on its own.

2. One line on your page

<script async src="https://mailyte.com/embed/v1.js"
  data-mailyte-form="frm_8kq2xv9btn4lw0"></script>

The form appears where the tag is and sizes itself. To put it somewhere else, add <div data-mailyte-form-mount="frm_8kq2xv9btn4lw0"></div> where it should go.

To open it over the page from a button instead, add data-mode="popup" to the script tag, then:

<button data-mailyte-open="frm_8kq2xv9btn4lw0">Join the waitlist</button>

Your page can react when someone signs up:

Mailyte.on('submitted', function (e) {
  // e.formId, e.status: "subscribed" or "pending_confirmation"
});

If you prefer a plain iframe, the Share tab has that too: https://mailyte.com/embed/f/<form id>. It posts { type: "mailyte:resize", formId, height } to your page whenever its size changes.

3. Your own HTML

Full control of the styling, no script needed:

<form action="https://app.mailyte.com/api/v1/public/forms/frm_8kq2xv9btn4lw0/submissions" method="post">
  <input type="email" name="email" required>
  <input type="text" name="name">
  <input type="hidden" name="campaign" value="launch-oct-2026">
  <input type="text" name="website" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
  <button>Join</button>
</form>

After a signup the browser goes to the thank-you page you set on the form, or to ours. If the signup is refused, it comes back to your page with ?mailyte_error=<reason> (the reasons are below).

Leave the website field in and empty. People never see it; bots fill it in, and we quietly drop those signups.

Turn off the bot check for this form. On the form's Setup tab, Check that each person is not a bot uses Cloudflare Turnstile, which only works on our own pages (the link and the iframe). A form on your site cannot pass it. The honeypot, the limits below and the address check still protect it.

4. From your own code

const res = await fetch('https://app.mailyte.com/api/v1/public/forms/frm_8kq2xv9btn4lw0/submissions', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
  body: JSON.stringify({
    email: 'ada@example.com',
    name: 'Ada Obi',
    campaign: 'launch-oct-2026',
    fields: { company: 'Acme Ltd' },
    consent: true,
    page_url: location.href,
  }),
});
Field Notes
email Required. Up to 254 characters.
name Optional, or required if the form says so. Up to 120 characters.
campaign Optional label for the push that brought them, up to 100 characters. Taken from utm_campaign when you leave it out.
fields Your contact fields. Only the ones the form asks for are accepted. metadata is accepted as another name for it.
tags Optional. Only the tags listed under Tags your website may add on the form.
consent Required when the form shows a consent box. We keep the exact text the person was shown.
page_url, referrer, utm Optional. UTM tags in page_url are read too.
website The honeypot. Leave it empty or out.

What comes back

A signup the form accepts is always 202, with the same answer whether the address is new or already on your list, so the form cannot be used to find out who your subscribers are:

{ "data": { "status": "pending_confirmation", "message": "Almost done. We sent you an email...", "redirect_url": null } }

status is subscribed (they joined) or pending_confirmation (they must press the button in the email we sent first).

A refusal carries an error_code and a message that is safe to show the person:

Code HTTP Meaning
invalid_email 422 Not written correctly, or a mistyped domain. data.suggestion may hold the fix, like ada@gmail.com: offer it, never apply it yourself.
undeliverable_email 422 The domain cannot receive mail, or the mailbox is known not to exist.
disposable_email 422 A temporary address.
role_address 422 A shared address like info@ (only on forms set to Strict).
consent_required 422 The consent box was not ticked.
validation_failed, unknown_field, tag_not_allowed 422 Something else in the body; errors says what.
origin_not_allowed 403 The page is not on the form's list of websites.
captcha_failed 403 The bot check did not pass.
form_closed 410 The form was closed.
rate_limited 429 Too many tries. Wait Retry-After seconds.

Confirming their email

Newsletter forms ask people to confirm by default. We email a button from the sender you chose; the person joins your list only when they press it, on a page at mailyte.com/confirm. Opening the link does nothing by itself, so email security scanners that open every link cannot confirm for them.

Until they confirm, nothing is added to your contacts: you see them on the form's Signups tab as Waiting to confirm. The link works for 48 hours.

Someone who unsubscribed before always has to confirm, whatever the form says. Pressing the button is how they come back, and it lifts their unsubscribe so your emails reach them again.

Websites that may post

On the Setup tab, Websites that may post to this form takes one site per line, like https://acme.com. Empty means any website. It is a browser rule: a page on another site is refused, but a script calling the address directly is not, so it does not replace the other protections.

Testing two versions

On any paid plan, the form's A/B test tab shows half your visitors (or the share you choose) a different title, description, button or colour. Each visitor always sees the same version. The tab shows views, signups and the rate for each, and says when one is ahead for real (95% sure, after at least 100 views each). Keep the winner and its words become the form's.

If you post to the form from your own code, nothing changes for you: a test only runs on our hosted page, the one-line script and the iframe. Signups from your code are not part of it.

Webhooks

Subscribe a webhook to any of these to feed your own system:

Event When
form.submitted Every signup the form accepted, including people already on your list.
contact.subscribed Someone joined through a form, at once or by confirming.
contact.confirmed Someone pressed the button in the confirmation email.

Each carries the form (id, name, type), the contact, the campaign, utm, fields and the consent record. See Receiving webhooks for signing.

From your server instead

If the signup reaches your backend first, you do not need a form at all: add the person with PUT /api/v1/contacts and an API key with the contacts:write scope, sending the list in list_ids and the consent evidence in consented_at and consent_source. See Checking contacts added through the API.