Contacts & sending from MailInApp
Besides exporting HTML for your ESP, you can send finished emails directly from the dashboard — to a single contact, a selection, or a whole list. Delivery still runs through your own SMTP relay, so your sending domain and reputation stay yours.
Contact lists
The Contacts tab holds your lists. Every list has an email column (that's what makes it sendable) plus any columns you like — name, plan, city — and each column doubles as a {{field}} merge tag in the studio.
A contact list is a data source like any other. Connect it in the studio's Data panel with the Audience role, and it becomes both who the email sends to and where bare {{field}} merge tags get their per-recipient values — the canvas previews with real contact data. See Data sources & merge tags.
Import a CSV
Already have your contacts somewhere else? Import CSV (on the Contacts tab, or inside a list to append to it) accepts the files ESPs and spreadsheets export: comma-, semicolon- or tab-separated, with a header row. Quoted values, Excel BOMs and "" escapes are handled.
The header names become your columns. If no column is named email, the import looks for the column that actually contains addresses (e.g. "E-Mail Address") and uses it as the email column. Nothing is stored until you hit save, so you can review — and fix — the table first.
Export a CSV
Download CSV on a list (and on the suppression panel) gives you a snapshot backup any time — useful before a big filtered send, or just to keep an off-platform copy. It round-trips through the same importer above.
Configure sending once
Direct sends need a sending method configured under Settings, in one of two ways:
- No ESP yet? Verify your own domain and MailInApp sends on your behalf — no SMTP credentials to generate or rotate. This is the recommended default if you don't already have an SMTP-capable provider.
- Already have an ESP? Point MailInApp at its SMTP relay — SendGrid, Brevo, Mailgun, Amazon SES, even a Gmail app password for small tests. Any provider with an SMTP endpoint works. Use Test & save to verify the connection before your first send. Your SMTP password is a credential: it is stored server-side only and comes back masked everywhere.
Both methods feed the same send pipeline — manual sends, scheduled sends, and everything else on this page behave identically either way.
Send a test first
Before sending to your list, use Send test to mail yourself (or anyone) a fully rendered copy. It's prefilled with your account email, prefixed [Test] in the subject, and carries a real working live-view link, so interactive blocks are actually testable. A test send doesn't touch your data source, doesn't count against the 200-recipient cap, and doesn't appear in send history.
Send
From an email's card on the dashboard, hit Send:
- The recipients come from the email's audience — the contact list connected with the Audience role in the studio's Data panel. No list linked yet? Pick one right there.
- Choose everyone, or tick individual contacts — sending to one contact is just a one-row selection. Rows you've already sent to are labeled with the date of their last send, so you don't lose track of who's been reached (this is informational only — it doesn't change your selection for you).
- Set the subject (merge tags work there too, and ✨ Suggest drafts a few options with AI — see AI-assisted copy) and send.
Every recipient gets their own compiled email: merge tags resolved from their row, and a personal signed live view link. Interactive blocks render in their static fallback tier with link-first interactions. Poll options and ratings are links that confirm before recording; forms and carousels open the hosted live view. The email works in every client, and nothing is ever recorded from a bare link prefetch.
A/B testing
Add up to 4 more variants (2 to 5 total) to split-test a send. Each variant can change the subject, the sender identity, or, by pointing it at a different project, the entire email content. Every recipient is deterministically assigned one variant based on their email address, so retries and resends never reshuffle who saw which one.
Optionally turn on Pick a winner automatically to mail only a sample first and have MailInApp mail the best-performing variant to everyone else once enough data comes in. See A/B testing & automatic winners. Recurring sends stay single-variant for now; there's no variant picker on the schedule form.
Segment your audience
Above the recipient list, Audience filter builds a rule (e.g. ticket_status = Resolved and resolved_date within the last 7 days) against your data source's fields, then Apply filter to selection sets your selection to whatever currently matches — a rolling segment instead of a one-off manual pick. Combine rules with AND/OR the same way you already do for a block's visibility conditions in the studio.
Soft-launch a send
Soft launch samples a random slice of your current selection — set a percentage and hit Sample to send to that fraction first. Once you're happy with the results, Select remaining (not yet sent) picks up everyone in the list who hasn't received this email yet, so the follow-up wave never double-sends to the sample.
Discount offer
A project whose audience is a Shopify or WooCommerce-connected contacts list gets a Discount offer section on the Send page — the pairing for a Purchase history segment: match past buyers, then actually give them something.
Turn it on and set a percentage or fixed amount off, how many days the code stays valid, and, optionally, scope it to one product instead of the whole store. Right before each matched recipient's email renders, MailInApp mints a real, single-use discount code on your own connected store: a Shopify discount code or a WooCommerce coupon, restricted to that one customer's email. The code and its ready-to-click redemption link (straight to your store's own checkout, with the code already applied) are available as the {{discount_code}} and {{discount_url}} merge tags. Drop them into a button, a heading, or anywhere else text already works. See Built-in tags.
A few things worth knowing:
- Redemption happens entirely on your own store's checkout — not MailInApp's own Product block checkout. This is deliberate: an audience segmented from real store purchases is already shopping there, so the discount reuses that store's existing inventory, tax and pricing rules instead of a second point of sale.
- Set Don't re-offer within N days to stop the same contact from getting a fresh code from an unrelated campaign too soon after their last one — useful once more than one project targets the same list. Leave it blank and it defaults to the code's own expiry, so a replacement never goes out while the old code might still be valid.
- A recipient's order still attributes back to the campaign that sent the code the same way any other store click does — see Tracking Shopify orders, checkouts & revenue.
- If minting fails for a recipient (a store isn't connected, or a token needs reconnecting), that recipient's email still sends — just without a code. Nothing about a discount offer can block a send.
- Recurring schedules pick this up the same way a manual send does, minting a fresh code on every run — a rolling-window schedule never hands out a stale code from an earlier occurrence.
Recurring sends
Some audiences change over time — "resolved 3–7 days ago" matches different rows every day. A Recurring send re-evaluates your audience filter against the linked data source's current rows on a schedule (daily or weekly, at a UTC hour you choose). It sends only to matches that haven't already received this email, picking up where the last run left off. Each run writes a normal send-history record, same as a manual send. A schedule whose relay fails on every recipient for three consecutive runs disables itself automatically rather than silently burning through your list.
Push contacts from your helpdesk or CRM
Instead of re-exporting a CSV before every send, a contacts list can accept pushes directly. Mint an API key for the list under its settings in the Contacts tab, then have your own system (a helpdesk automation, a CRM workflow) call:
POST /api/datasources/<datasourceId>/rows
Authorization: Bearer <apiKey>
Content-Type: application/json
{ "rows": [{ "email": "[email protected]", "ticket_status": "Resolved" }] }
Rows are upserted on lowercase email — a known address updates in place, a new one appends — and existing rows are never reordered or deleted, since response attribution is positional (row:<n>) and reordering would break historical joins. Batches are capped at 500 rows and rate-limited per key. The key is shown once when minted (or regenerated) and masked everywhere after, like every other credential in MailInApp; Revoke disables the integration without touching existing rows.
Send history
Every real send (not test sends) is recorded and shown above the recipient picker — date, subject, and how many were sent, failed, or skipped. Nothing is ever deleted client-side on refresh; history is the source of truth for what's actually gone out.
Unsubscribe & suppression
The Footer preset includes an {{unsubscribe_url}} merge tag that resolves to a one-click unsubscribe page for that specific recipient. Unsubscribing is account-wide: it suppresses the address from every project you send from, not just the one they clicked from — so a recipient never has to opt out of each campaign separately.
Addresses your SMTP relay rejects synchronously at send time (invalid mailbox, relay refusal) are suppressed the same way, so a dead address isn't retried on every future send. (A relay accepting a message and bouncing it later, asynchronously, isn't observable without an ESP-specific webhook — that's out of scope today.)
Suppressed addresses show a bounced or unsubscribed badge on the Contacts tab. Both are resubscribable — a full mailbox or a stray click isn't necessarily permanent — and suppressed rows are automatically skipped (and reported separately from failures) on future sends.
After the send
Exactly like an ESP send: interactions flow into responses, grouped per contact, and your webhook fires per event if you configured one.
Limits
- Up to 200 recipients per send (one list, one run of a recurring schedule). For bigger audiences, use the export & ESP workflow; a recurring send also just catches up whoever's left on its next run.
- Rows without a valid email address are skipped and reported after the send.
- Importing a CSV drops rows whose email address already appeared earlier in that same file, so re-importing an overlapping export doesn't create duplicate sends — you'll see a "Skipped N duplicate email(s)" notice.
- The contacts push API caps each batch at 500 rows.