API documentation
Four steps: register an app, get its key, post a submission, read the response.
1. Register your app
On the dashboard, Register an app is seven steps. The first three are required, the next three are optional and can be switched on later, and the last one issues the key.
- Basics — the website name, and the email address submissions should be delivered to. Any inbox works.
- Form fields — the fields you need from your form. Only these are emailed; anything else your form posts is refused with
400 unknown_field(Max: 25 fields). - Mail design — one of 5 designs for how a submission looks in the inbox. Switchable any time.
- Auto-reply (optional) — a message emailed back to whoever submitted the form.
- Spam guard (optional) — a bot fills every input it finds and submits instantly, while a person leaves a hidden field alone and takes a few seconds. Name a honeypot field and a timing field your form posts and either signal refuses the submission with
422. - Attachments (optional) — let submissions carry files.
- Generate key — creates the app and shows the secret key, once.
2. Post the submission
Without files — JSON
const res = await fetch("https://mail.satz.co.in/api/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Jane Doe",
email: "jane@example.com",
message: "Hello!",
}),
});With files — multipart
const res = await fetch("https://mail.satz.co.in/api/v1/send", {
method: "POST",
headers: { "Authorization": `Bearer ${SECRET_KEY}` },
body: new FormData(form),
});
if (res.ok) form.success(); // your functionWith attachments on, the whole request may total 5.0 MB — fields and every file together, not per file — across at most 5 files. .doc/.xls are not accepted.Supported formats: .png, .jpg, .jpeg, .gif, .webp, .pdf, .txt, .csv, .docx, .xlsx, .pptx.
3. Read the response
Always JSON plus a status, never a redirect — so what a visitor sees next is your decision. Success is { "ok": true }; every failure carries a machine-readable error code, and the two field errors name the field.
| Status | Meaning |
|---|---|
200 | The email was sent to the configured address. |
200 | duplicate: true — an identical submission within 60s; no second email sent. |
400 | Empty or invalid body. |
400 | unknown_field — the submission contained a field the app doesn't declare. |
400 | body_too_deep — the body nests more than 5 levels. |
401 | Secret key missing or invalid. |
403 | destination_unverified — the destination address hasn't confirmed yet. |
413 | payload_too_large — the request body exceeded 500KB, or 5.0 MB for an app with attachments switched on. |
422 | honeypot_filled — the app's honeypot field arrived non-empty. |
422 | too_fast — submitted faster than the app's minimum fill time. |
422 | timing_missing — the timing field held nothing usable. |
422 | spam_rejected — the content scored past the spam threshold. |
422 | attachments_not_enabled — a file was posted but the app doesn't accept them. |
422 | too_many_files — more files than the app's limit. |
422 | unsupported_file_type — the file's type isn't accepted, or its contents don't match its name. |
422 | empty_file — one of the files was zero bytes. |
422 | invalid_filename — a file arrived with no extension. |
429 | daily_limit_exceeded — the app has used its 500 sends for the day. |
502 | The mail server failed to send. |
Important notes
- 500 emails a day per app, on the UTC calendar day. Past it, submissions are refused with
429rather than dropped silently — ask us at contact@satz.co.in if your form needs more. - Repeats are collapsed. An identical submission within 60 seconds answers
200with"duplicate": trueand sends no second email, so a double-clicked submit is safe. A failed send is not a repeat, so retrying after a502does deliver. - Body size is capped at 500KB, or 5.0 MB with attachments on, and nesting at 5 levels.
- Content is scored for spam on every submission — link volume, anchor or BBCode markup and mail-header probes →
422 spam_rejected, recorded in the app's activity with the reason. A honeypot field and a minimum fill time are available per app on the dashboard; neither belongs in your field list, since both are stripped before the field check runs. - A blocked submission costs nothing from the daily allowance. Anything that reaches the mail server counts, including a send that fails there.
From:is always our address and the submitter goes inReply-To:, so replying from your inbox reaches them. Sending as their address would fail SPF and DMARC.- An optional automatic reply to the submitter can be switched on per app. It is a second email and uses a second send; if the day's allowance runs out, the submission still goes through and the reply is the part skipped.
- Rotating the key invalidates the old one immediately. Only a hash is stored, so a lost key cannot be recovered — rotate and update your integration.
- Delivered submissions are not stored. The activity log keeps the status, the time and the mail server's reply, not the fields you received. Only a blocked submission records what triggered the block.
- Every attempt is logged against the app, successes and failures alike, so a missing email is diagnosable from the dashboard.
Try it
Sign into send a real test email from this page using one of your app’s secret keys.