Send contacts and events from your app
The AguMail API is on every plan, the free one included. Use it to add people to your list from your own website or app, tag them, check who is subscribed, and record what they do, so a segment can count it and a flow can start from it.
Selling online? Orders, carts and products are the store half of the API, on the Commerce plans. Connect any other store walks through it.
1. Create an API key
Section titled “1. Create an API key”In AguMail, go to Settings → Store & API → API keys → Create key and choose Other store or your own code. Copy the key; it is shown once. Every request below sends it as a header:
Authorization: Bearer agm_…Content-Type: application/json2. Add contacts
Section titled “2. Add contacts”POST /api/v1/contacts{ "contacts": [ { "email": "sam@example.com", "firstName": "Sam", "lastName": "Rivera", "tags": ["Newsletter", "Yoga"], "customFields": { "plan": "Monthly", "studio": "Riverside" }, "consent": { "optedIn": true, "source": "website" } }] }- Only send
optedIn: true, or aconsentedAtdate, for people who agreed to marketing email. A new contact sent without either is saved as Never subscribed: they are on your list, and your campaigns and flows do not email them. - Sending an address AguMail already has updates that contact. Nobody is added twice.
tagsadds each tag the contact does not have yet, and a flow that starts on Tag Added starts for them. Up to 20 tags per contact.customFieldsare your own fields, up to 30 per contact. Segments can filter on them and emails can print them.timezone(for example"Europe/Lisbon") lets a campaign arrive at the same local time for everyone.
The response counts what happened: { "ok": true, "created": 1, "updated": 0, "suppressedSkipped": 0, "overCapacity": 0, "confirmationsSent": 0, "unknownContacts": 0, "conflicts": 0, "conflicted": [] }. suppressedSkipped counts people who unsubscribed or can no longer be emailed; sending them again does not resubscribe them. overCapacity counts new contacts your plan had no room for. confirmationsSent counts people who had unsubscribed, asked to come back, and were sent an email to confirm it. They are in suppressedSkipped too, because they stay unsubscribed until they click the link in it. The last three are about your app’s user ids, below.
3. Check who is subscribed
Section titled “3. Check who is subscribed”If your own app keeps a “send me emails” checkbox, ask AguMail where each person stands and keep the two in step:
POST /api/v1/contacts/status{ "emails": ["sam@example.com", "alex@example.com"] }{ "ok": true, "contacts": [ { "email": "sam@example.com", "userId": null, "status": "active", "subscribed": true }, { "email": "alex@example.com", "userId": null, "status": null, "subscribed": false }] }subscribed is the one to act on: it is true only for someone who is subscribed and has not unsubscribed or bounced since. status is null for an address that is not on your list. The answer covers exactly the addresses you asked about, up to 500 at a time.
4. Record what people do
Section titled “4. Record what people do”Anything your business records can be sent as an event: a booked appointment, a finished course, a renewed membership.
POST /api/v1/events{ "events": [ { "email": "sam@example.com", "metric": "Booked appointment", "occurredAt": "2026-09-19T10:15:00Z", "uniqueId": "appt_991", "value": 49, "properties": { "service": "Cut & finish", "staff": "Ana" } }] }metricis your own name for what happened. Spelling and capitals do not matter: “Booked appointment”, “booked_appointment” and “Booked Appointment” are one event.uniqueIdis your name for this particular occurrence, such as an appointment id or an invoice number. Send it and you can safely retry: the same id is never recorded twice, and nobody is enrolled in a flow twice for it.occurredAtis when it happened, not when you sent it. Leave it out and AguMail uses now.propertiesis anything you want the email to be able to say. Up to 30 keys.valueis money, if the event is worth some.
Then, in AguMail:
- Segment on it. In the segment builder pick Did something…, choose your event, and set a count and a window, such as “at least 1 time in the last 30 days”. Add narrow by property to ask about one of your own properties, for example
service is Cut & finish. - Start a flow with it. In the flow editor choose the Any recorded event trigger and pick your event. It appears in that list once the first one has arrived.
- Print it in the email.
{{event.service}}and{{event.staff}}render what that contact’s event carried. A missing property renders as nothing rather than breaking the email.
The response counts what happened: { "ok": true, "created": 1, "duplicates": 0, "unknownContacts": 0 }. Watch unknownContacts: an event for someone AguMail does not hold is skipped rather than creating a contact, because an event is not permission to email someone. Send the person to /api/v1/contacts first.
Identify by your app’s user id
Section titled “Identify by your app’s user id”If your app gives each user an id, send it as userId beside the email. AguMail then knows the person by that id, so when they change their email in your app, your next call moves the same contact to the new address. Their status, tags, history and flows stay with them, and they are not counted twice.
POST /api/v1/contacts{ "contacts": [ { "userId": "user_8412", "email": "sam.rivera@example.com" }] }userIdis your own id for the person, any text from 1 to 200 characters. It is set once: a contact keeps the first id it is sent with.- A contact AguMail already has by email takes the id the first time you send both.
- With a
userId, the email is optional. A record with only auserIdupdates that contact. If nobody has that id it is skipped and counted inunknownContacts, because a new contact needs an email. - A new address that is on your suppression list stays blocked. The contact keeps its status, and nothing is sent to that address.
- The unsubscribe link in an email sent before the change still works, and unsubscribes the same contact.
Two kinds of record are refused, so that two people are never mixed up: a new email that already belongs to another contact (email_taken), and an email whose contact already has a different id (user_id_mismatch). conflicts counts them, and conflicted lists each one by its place in your request, starting from 0:
{ "ok": true, "created": 0, "updated": 1, "suppressedSkipped": 0, "overCapacity": 0, "confirmationsSent": 0, "unknownContacts": 0, "conflicts": 1, "conflicted": [ { "index": 1, "userId": "user_77", "email": "alex@example.com", "reason": "email_taken" } ] }Events and the status check take the id too:
- An event can carry
userIdin place ofemail, or beside it. With auserId, the id alone says who the event is about. An event never changes anyone’s email or id. /api/v1/contacts/statustakesuserIdsbesideemails, up to 500 in all. It answers the emails first and then the ids, and every answer carries bothemailanduserId.
Delete a contact
Section titled “Delete a contact”When someone deletes their account in your app, delete them from AguMail too:
POST /api/v1/contacts/delete{ "contacts": [ { "userId": "user_8412" }, { "email": "alex@example.com" }] }{ "ok": true, "deleted": 1, "notFound": 1, "conflicts": 0, "conflicted": [] }- Name each person by
userId,email, or both, up to 500 in one request. It is aPOSTbecause some tools drop the body of aDELETErequest. - The contact is deleted with its tags, custom fields and history. Any flow they are in stops for them at once, and they stop counting toward your plan straight away.
notFoundcounts people AguMail does not have, including anyone you already deleted. It is never an error, so you can safely send the same request again.- With both
userIdandemail, the two must belong to the same contact. If they belong to two different contacts, neither is deleted, and the record is listed inconflictedwith one of the reasons above. - Unsubscribes are kept. AguMail remembers by address who unsubscribed, marked an email as spam or bounced. If you add the same email again later, someone who unsubscribed stays unsubscribed and is counted in
suppressedSkipped. Someone who never unsubscribed can be added again like anyone new. - Campaign and flow reports still count the emails the person was sent.
Loading your history
Section titled “Loading your history”When you first connect, you can send the people and events you already have, so segments start from the whole picture. Mark those batches as history with "backfill": true:
POST /api/v1/contacts{ "backfill": true, "contacts": [ { "email": "sam@example.com", "customerSince": "2024-03-02T09:00:00Z", "consent": { "optedIn": true, "source": "website" }, "consentedAt": "2024-03-02T09:00:00Z" }] }The flag works the same on /api/v1/events. History is stored like any other data, but it starts no flows: nobody gets a welcome or an event-triggered email for something that happened before it reached AguMail.
customerSince becomes the contact’s Date added when AguMail creates them, so flows timed from Date added do not treat history as new. consentedAt is the date they opted in to marketing email. Send it only for people who opted in, because AguMail counts it as an opt-in.
AguMail applies the same rule without the flag: an event whose occurredAt, or a contact whose consentedAt, is more than 72 hours old is treated as history anyway. Leave the flag off for what happens from now on, so your flows can react to it.
Limits and errors
Section titled “Limits and errors”- 120 requests a minute per key, and up to 500 records per request.
400means a field is missing or malformed; the message names it.401means the key is missing, wrong or revoked.402means the request is part of the store half of the API (/api/v1/connect, orders, carts and products), which is on the Commerce plans. Everything on this page works on every plan.429means you are over the rate limit. TheRetry-Afterheader, andretryAfterin the body, say how many seconds to wait before you send the request again.