I want to set up Mail-o-Mail so I can send emails directly from this conversation.
Read the API specification at:
https://mail-o-mail.com/api/openapi.json
This is a REST API. Base URL: https://mail-o-mail.com
Authentication: Authorization: Bearer <API_KEY> (X-API-Key also accepted). You'll get the key during signup.
Here's what I need you to do:
1. SIGN UP: Call POST /api/v1/ai-signup with my email address below.
I'll give you the 6-digit verification code from my inbox.
Then call POST /api/v1/ai-verify with the code to complete signup.
This returns my API key — use it as an Authorization: Bearer header
(X-API-Key also accepted) for all subsequent calls.
2. ADD MY DOMAIN: Call POST /api/v1/domains with my domain name.
You'll get back DNS records (verification TXT, DKIM CNAMEs, SPF,
DMARC) AND an auto_setup block. If auto_setup.supported is true, give
me auto_setup.setup_url to click — I approve the records at my DNS
provider (GoDaddy, Cloudflare, IONOS …) with one click and the domain
verifies itself; GET /api/v1/domains/{domain}/auto-setup re-mints the
link. Otherwise tell me exactly what to add to my DNS. After I
confirm, call POST /api/v1/domains/{id}/verify to check verification.
3. ADD FROM-EMAIL: Call POST /api/v1/from-emails to add a sender
address on my verified domain (e.g.
[email protected]).
4. SEND A TEST: Call POST /api/v1/send to send me a test email
confirming everything works. (An optional "consent" field is accepted on
email sends: omit it to send as usual, but if you set it to no/false/0 the
send is refused. Use it when sending to real recipients who opted in.)
To send an email with a file attached, use POST /api/v1/send-attach
instead — multipart/form-data with fields to, subject, body,
from_email_id, and file (one file, max 10MB).
5. SHOW ME BULK: Explain how to run a campaign:
- POST /api/v1/templates to create a template. DON'T put an unsubscribe
link or compliance footer in the HTML — Mail-o-Mail appends its own at
send time. The response returns template_number: use that everywhere
a template is identified (send-template, send-to-tag, and
GET/PATCH/DELETE /api/v1/templates/{template_number} to read the
stored HTML back, fix a line in place, or delete it — a delete is
refused while a sequence or scheduled campaign still uses it).
- POST /api/v1/contacts/upload to upload contacts with a tag (an
upsert: rows match existing contacts by email OR phone; omitted
consent stays untouched on existing contacts; same-named custom
fields are overwritten and the response counts them)
- THE VARIABLE CONTRACT (guided send flow): GET
/api/v1/templates/{template_number} first and read `variables` — the
exact placeholders the template requires. Put those values on the
contacts as custom fields with the SAME keys (upload_contacts /
PATCH /api/v1/contacts). Then POST /api/v1/campaigns/send-to-tag to
send to that tag: variables resolve per contact — core fields
(email/phone/name/company), then the contact's custom fields by bare
; a core field wins over a same-named custom field when it is
non-empty. A contact missing ANY required variable is SKIPPED — not
sent, not charged — and named in skipped_missing_variables; tell me
who was skipped and why. Only "on_missing_variables": "send_blank"
sends to them with empty values, and only if I ask for it.
- Explicit-recipients shape instead: POST /api/v1/send-template per
recipient with every required variable in `variables` (a missing key
fails by name: missing_variables) — never send a blank.
- To send LATER instead of now, add "scheduled_at" to the same POST
/api/v1/campaigns/send-to-tag body — ISO 8601 WITH an explicit UTC
offset (e.g. 2026-09-10T09:00:00+05:30), at least 5 minutes and at most
90 days ahead (not with send_mode intelligent). Run it with
"dry_run": true first and show me the time and the numbers; they are a
projection — who receives it, who is skipped and what it costs are
resolved when it fires, and credits move only then. GET
/api/v1/campaigns/scheduled lists what is scheduled; POST
/api/v1/campaigns/scheduled/{scheduled_id}/cancel cancels a pending one
(no confirmation needed — cancelling prevents a send). There is no
edit: cancel, then schedule again.
- GET /api/v1/campaigns/{id}/status to track delivery (skipped rows
show as skipped_missing_variables with their missing names)
6. WHATSAPP (optional): I can also send WhatsApp messages.
- GET /api/v1/whatsapp/status to check if my WhatsApp is connected.
- Connecting WhatsApp needs ME in a browser (Meta Embedded Signup), but
you can start it: POST /api/v1/whatsapp/connect-intents returns a
connect_url — hand it to me, then poll
GET /api/v1/whatsapp/connect-intents/{token} every 30-60 seconds until
it's completed. When it completes, ASK me whether I want a webhook set
up so my own systems receive incoming WhatsApp messages: if yes, agree
the HTTPS URL with me, then POST /api/v1/whatsapp/webhook — the
response shows the signing secret ONCE, so relay it to me immediately.
Prove it works with POST /api/v1/whatsapp/webhook/test, and debug with
GET /api/v1/whatsapp/webhook/deliveries.
- GET /api/v1/whatsapp/templates to list my templates (only APPROVED ones
can be sent; components come back as a parsed object), or
POST /api/v1/whatsapp/templates to create one and submit it to Meta for
approval — buttons are a typed union (QUICK_REPLY{text} | URL{text,url
[,example]} | PHONE_NUMBER{text,phone_number}); for an image/video/
document header first call POST /api/v1/whatsapp/media with
purpose "template_header" (base64 or a public https url) and pass the
returned header_handle. The template also appears in my dashboard. Poll
GET /api/v1/whatsapp/templates/{name}/status for approval.
- POST /api/v1/whatsapp/media with purpose "send_media" hosts an
image/video/pdf publicly (quota-limited, removed after 30 days if no
send used it) and returns the url for a media-header send.
- POST /api/v1/whatsapp/send-template to send an approved template to a
phone number. Consent: with "consent" omitted the STORED contact's
consent decides (stored no → no_consent, unknown contact →
consent_unknown); pass "consent": "yes" only when the person opted in.
- POST /api/v1/whatsapp/send-message to send FREE-FORM text (optionally
up to 3 quick-reply buttons) WITHOUT a template — only allowed within
24 hours of the customer's last message to me (WhatsApp's
customer-service-window rule). Outside the window it refuses with 422
window_closed and nothing is sent — fall back to send-template then.
- GET /api/v1/whatsapp/stats for delivery and read statistics, and
POST /api/v1/whatsapp/send-otp to deliver one-time passcodes (I
generate and verify the code — Mail-o-Mail only delivers it).
7. AUTOMATE (optional): I can also set up Triggers — keyword-based
auto-replies for web chat and WhatsApp (e.g. STOP, PRICE, ORDER STATUS)
that confirm with Yes/No, reply with a message, or call my own API.
Triggers are built and managed from the dashboard, not this API — there's
no endpoint for creating one yet. If I ask about automations, explain
what Triggers can do and send me to https://mail-o-mail.com/triggers to
build one visually.
8. SEQUENCES (optional): I can also build automated drip sequences —
multi-step flows that enroll a contact and message them over time.
- GET /api/sequences to list mine, or POST /api/sequences to create one
(name + entry tag). A flow is a chain of nodes — message, confirm,
condition, wait, email, api_call, update_consent_no, and end — so it
can branch, pause, honor opt-outs, and send across channels. Creating a sequence only saves it as a
draft; nothing sends until it's turned on.
- POST /api/sequences/{id}/status with {"status": "active"} to turn it
on (or "inactive" to pause it). Activating needs the Triggers add-on
enabled on the account — if it isn't, the call comes back
addon_required and I should point the user at Admin → Home to enable it.
- POST /api/sequences/{id}/enroll to add one contact — by contact_id,
email, or phone (whichever I have).
- GET /api/sequences/{id}/executions to see who's enrolled, what step
they're on, and how each run finished.
In short: create it, turn it on, enroll — all from your assistant.
9. MANAGE MY CONTACTS: full single-contact CRUD. Identify a contact by
email and/or phone — never an internal id.
- POST /api/v1/contacts to create one (name, company, tags, consent
yes/no, custom fields). The response says honestly whether it was
"created" or "merged" into an existing contact with that email/phone.
POST /api/v1/contacts/batch takes up to 20 at once (per-item results).
- PATCH /api/v1/contacts to update — changes go in a "set" object
(name, company, email, phone, custom; a null custom value deletes
that key). If a new email/phone collides with another contact it
answers would_merge — ask me before repeating with "merge": true.
- POST /api/v1/contacts/tags to tag one contact (missing tags are
auto-created); DELETE /api/v1/contacts/tags to untag one.
Tag management: POST /api/v1/tags/rename (merge:true folds a tag into
an existing same-named one), DELETE /api/v1/tags (refused while a
sequence or scheduled campaign uses it), POST /api/v1/tags/retag to
apply a tag to everyone carrying another tag.
POST /api/v1/contacts/consent/bulk flips consent for up to 1,000
listed contacts or a whole tag — confirm with me first.
GET /api/v1/contacts/stats and
GET /api/v1/contacts/detail for engagement + the full record;
GET /api/v1/contacts/messages for their last chat messages.
- POST /api/v1/contacts/consent/enable or /disable to flip marketing
consent — auditable, and the response is honest: consent yes with an
active unsubscribe still can't be emailed (sends_allowed: false).
- DELETE /api/v1/contacts erases the contact AND their chat history —
confirm with me first. Unsubscribes always survive a delete, so a
removed address can never be accidentally re-mailed.
BEFORE A BIG SEND (verify, then confirm — never guess):
- PREVIEW: POST /api/v1/campaigns/preview with the template_number and one
real contact's email — it renders the email exactly as the send will
(same renderer), returns which resolved and which came out
blank, and costs nothing. Show me the subject and the unresolved list.
- AUDIENCE: GET /api/v1/contacts/by-tag?tag={name} (or tag_id) lists who
carries the tag with consent, suppression and sendable flags plus honest
totals. Tag names match case-insensitively and trimmed, whole name only;
an unknown tag is tag_not_found, an empty one is tag_empty.
- NUMBERS: POST /api/v1/campaigns/send-to-tag with "dry_run": true returns
the resolved recipient_count, credits_required, without_consent and
suppressed counts without sending. Show me those numbers and wait for my
explicit yes before the real send.
- GET /api/v1/credits to check my balance first — 1 credit = 1 email or
1 WhatsApp message. Warn me if the campaign needs more than I have.
- GET /api/v1/recommendations/send-time?tag_id={id} tells you that
audience's best send window, computed from their last 90 days of email
opens, and GET /api/v1/recommendations/segment?tag_id={id} ranks the
tag's most-engaged contacts. Both are advisory — show me the summary
and let me decide. They say so honestly when there isn't enough data.
- To deliver at each recipient's personal best time, pass
"send_mode": "intelligent" to POST /api/v1/campaigns/send-to-tag.
PREPARE RECIPIENTS FOR A CAMPAIGN:
- Call GET /api/v1/templates/{template_number}/sample-excel to get a
download_url for a fillable spreadsheet. Its columns are exactly what that
template needs (phone, consent, plus any body variables, media_url for a
media header, and a dynamic-URL-button column). Give me the download_url so
I can open it, fill it with my contacts, and upload it when sending the
campaign. The link expires in about an hour — fetch a fresh one if needed.
MANAGING MY CONNECTION:
- CHECK: call GET /api/v1/whoami any time to confirm which account this key
is connected to (org, email, credits, WhatsApp status).
- DISCONNECT: call POST /api/v1/disconnect to disconnect this account, then
forget my API key. (Pass {"rotate_api_key": true} to hard-revoke the key
everywhere — it returns a new key.)
- RECONNECT: ask me for my API key again (I can get it from my dashboard) and
verify it with GET /api/v1/whoami, or run the signup steps above for a new
account.
Let's start! My email is: [YOUR EMAIL HERE]