How to send email with an API and track delivery
To send email with an API, make one POST request with the sender, recipient, subject, and body. Hail then records each email's events, such as delivered, bounced, or marked as spam. Read them through the API, the console, your AI assistant, or signed webhooks.
Every email API can send a message. What matters is what happens next: did it arrive, did it bounce, or did someone mark it as spam?
Hail tracks that for you. Every email Hail sends gets its own timeline, and your account gets delivery, bounce, and complaint rates. There's nothing to build first. You can send from your code, or ask your AI assistant to do it through MCP.
This guide sends one test email and follows it from start to finish.
Set up your sender
You need three things: a Hail account, an API key, and a verified sending identity. That identity can be your own domain or a Hail-hosted address. Emails use your prepaid balance. Top it up on the Usage & billing page in the console. Keep the key on your server, in an environment variable called HAIL_API_KEY.
Send your first email
You have two ways to send: the API or your AI assistant. Both use the same account, and both get the same tracking.
Send with the API
Swap both addresses in this request. The from address has to be a verified identity in your workspace. For this test, put your own inbox in to. It's an array, even for one recipient.
curl --fail-with-body --silent --show-error \
https://api.hail.so/v1/emails \
-H "Authorization: Bearer $HAIL_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: first-email-test-001' \
--data '{
"from": "agent@example.com",
"to": ["you@example.net"],
"subject": "Your first Hail email",
"body_text": "This is a test message from my application. Please reply so I can check the receiving side too.",
"recipient_consent": true
}'
recipient_consent: true says you have the right to email this person. That's true when you email yourself. Don't paste it into a real workflow unless it's true there too.
A success is HTTP 201 with an email object, and its status is sent. Save the id. The full response is in the Hail API contract.
Use a new idempotency key for each new email. If you rerun this request with the same key, Hail returns the first result and sends nothing.
Send with your AI assistant
Connect your assistant to Hail MCP. Then ask for the email in plain words. Replace both addresses, and use an inbox you own:
Use Hail to send a test email from agent@example.com to you@example.net. I own this inbox, so recipient consent is true. Then check the email's events and tell me if it was delivered.
Your assistant uses the same tracking as your code, through send_email, get_email_events, and get_email_stats.
See where every email is
Hail records what happens to each email, so you always know where it is. You don't add tracking code, and you don't need a webhook to start.
Set HAIL_EMAIL_ID to the id you saved. Then ask for the timeline:
curl --fail-with-body --silent --show-error \
"https://api.hail.so/v1/emails/$HAIL_EMAIL_ID/events" \
-H "Authorization: Bearer $HAIL_API_KEY"
Each event tells you one thing about that email:
| Event | What it means |
|---|---|
sent | Hail handed the email to the provider. |
delivered | The receiving server accepted it. |
delivery_delayed | Delivery is taking longer than it should. |
bounced | The receiving server turned it down. |
complained | The recipient marked it as spam. |
rejected | The provider refused to send it. |
opened | A tracking pixel in an HTML email loaded. |
clicked | A tracked link in an HTML email was clicked. |
A delivered event means the receiving server accepted the message. It doesn't mean the email reached the primary inbox.
You can follow the same timeline wherever you work:
- Console. Activity shows each email and its delivery trail.
- AI assistant. Ask "Was my last email delivered?"
- Webhooks. Your app can get
email.delivered,email.bounced,email.complained, andemail.send_failedas signed webhooks.
Track deliverability across all your email
One timeline tells you about one email. Your rates tell you if your email is getting through. Hail keeps them for you:
curl --fail-with-body --silent --show-error \
"https://api.hail.so/v1/emails/stats?bucket=day" \
-H "Authorization: Bearer $HAIL_API_KEY"
You get the last 7 days by default, with daily counts and rates for the whole range. To change the range, add from and to as ISO 8601 times with a timezone. The limit is 92 days.
- Delivery rate. Delivered emails out of all emails sent.
- Bounce rate. Hard bounces out of all emails sent.
- Complaint rate. Spam complaints out of all emails sent.
- Open and click rates. Unique opens and clicks out of all emails sent. These need HTML email.
The Email page in the console shows the same rates, with a chart and your recent bounces and complaints. Your assistant can read them too. Ask it how your email did this week.
What Hail handles for you
Tracking only helps if you can act on it. Hail does these jobs when you send:
- Authentication. Publish the DNS records for your custom domain. Hail then signs with DKIM and sends from
send.<domain>, so SPF and DKIM match yourFromaddress. - Bad addresses. After a hard bounce or a spam complaint, Hail adds the address to your suppression list and blocks future sends.
- Unsubscribes. An email to one recipient gets a one-click unsubscribe. Gmail and Yahoo can show it as a button.
Why you won't see an open event
This test email is plain text, and Hail needs an HTML body to track opens and clicks. That's where the tracking pixel and rewritten links go. You still get send and delivery events.
Even with HTML, an open is a hint, not proof that someone read your email. Some mail apps block images, and some automated systems fetch them, so the numbers can mislead you. A real reply tells you far more.
When something goes wrong
If the API rejects your request, read the error body first. Then check the key, the sender identity, and the required fields. These are the responses you'll see most:
402. Your balance is empty.403. The address is on your suppression list, or you hit a send cap. The error body says which.409. You reused an idempotency key with a different request, or the first request is still running. If you changed the request, use a new key. If it is still running, wait and retry with the same key.422. A field is wrong. Check thatfromis a verified identity andrecipient_consentistrue.502. The provider send failed. You get no email object, but Hail keeps the email. Find it withGET /v1/emails?status=failed.
Say the network drops and you can't tell if the email went out. Retry with the same idempotency key, so Hail doesn't send it twice. Hail also remembers a failed attempt under its key for 24 hours. After a failure, or after you change the request, use a new key.
If the email was sent but never shows up, check its timeline and the spam folder. On your own domain, run the email deliverability checker and look at the authentication headers on the email you received. A DNS record can look right while this one email still fails authentication.
Our DMARC policy guide explains the alignment check. The email API overview covers domain reputation, and why no single warmup deadline fits everyone.
Once sending works, reply from your test inbox. Check that the reply reaches your app. That's the start of an email workflow your agent can keep going.
Send your first email.
Connect your assistant and start sending email.