Engineering blog / Email · tutorial
Send an email with an API, then check what happened
Send a plain-text email with Hail, inspect the response, and follow delivery and bounce events without sending the message twice.
By Hail
Your send request succeeded, but the email has not appeared. Before retrying, find out what succeeded. A provider accepting the message, a receiving server accepting it, and a person reading it are different events.
Start with a plain-text email to an inbox you control. That gives you a small test you can inspect from both sides.
Set up the sender first
You need a Hail account with credits, an API key, and a verified sending identity. That identity can use your own domain or a Hail-hosted address. Keep the API key in a server-side environment variable named HAIL_API_KEY.
Replace both addresses below. The from address must belong to your workspace's verified email identities. The to field is an array, even when there is only one recipient. For this test, send to yourself; do not copy the consent flag into a workflow where it is untrue.
Send one plain-text email
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
}'
Keep the idempotency key when retrying this exact send after an uncertain network result. Use a new key for a new message. Generating a fresh key for every retry defeats the purpose: each attempt looks like another send.
The create-email endpoint returns HTTP 201 and an email object. Hail attempts the provider send before returning, so inspect the body's status: it can report sent or failed. Save the returned id and inspect end_reason if the send failed. The full shape is in the Hail API contract.
Follow what happened after sending
Set HAIL_EMAIL_ID to the returned id. This request retrieves the event history:
curl --fail-with-body --silent --show-error \
"https://api.hail.so/v1/emails/$HAIL_EMAIL_ID/events" \
-H "Authorization: Bearer $HAIL_API_KEY"
A later delivery, bounce, or complaint can change your understanding of the send. Your application can subscribe to signed webhooks instead of repeatedly checking the history.
Use the events to decide what happens next. A hard bounce is a reason to stop sending to the address and investigate it. A complaint should not trigger another attempt. A delivered event means the receiving server accepted the message; it does not prove that it appeared in the primary inbox.
Why there is no open event in this example
This email has a plain-text body. Hail's open and click tracking requires an HTML body, where a tracking pixel and rewritten links can be included. Plain-text email still produces send and delivery-related events.
Even with HTML, treat an open as a signal, not proof that a person read the text. Image blocking and automated fetching can make those signals incomplete or misleading. A reply is a different event with different meaning.
Debug the right layer
If the API rejects the request, check the error body, key, sender identity, and required fields. If it reports a failed send, inspect the returned error before changing your message copy.
If the message was sent but never appears, inspect the subsequent events and the receiving inbox's spam folder. For your own domain, run the email deliverability checker and inspect the received message's authentication headers. A DNS record being present does not establish that this particular message authenticated correctly.
Our DMARC policy guide explains the alignment check. The email API overview also covers domain reputation and why there is no universal warmup deadline.
Once sending is reliable, reply from your test inbox and confirm the inbound message reaches your application. That is the start of an email workflow your agent can actually continue.