Send email from Xano
An External API Request in a Xano function stack sends from your domain with your DKIM key, keeps the key in an environment variable, and returns a message id your table can keep.
Updated September 15, 2026
Xano's function stack has an External API Request step, and a Pigeon Atlas send is exactly one of those. The key goes in Xano's environment variables so it is never in a function, and the response comes back as an object the rest of the stack can use.
The environment variable
Settings → Environment Variables → Add: name PIGEONATLAS_API_KEY, value your key. Every function stack can read it as $env.PIGEONATLAS_API_KEY, and rotating the key later is one edit.
The request
In the function stack — an API endpoint, a background task, a trigger on a table — add External API Request:
| Field | Value |
|---|---|
| URL | https://pigeonatlas.com/v1/emails |
| Method | POST |
| Headers | Authorization: Bearer + $env.PIGEONATLAS_API_KEY, Content-Type: application/json |
| Params | an object (below) |
For Params, build an object with the inputs of the stack:
from = "Your App <hello@mail.yourapp.com>"
to = $input.email
subject = "Your order " ~ $input.order_number ~ " is confirmed"
text = "Thanks — order " ~ $input.order_number ~ " is on its way."
With Content-Type: application/json Xano sends the object as a JSON body. Use the ~ concatenation in the subject rather than a separate text step; it reads as one line in the stack.
The response, and the id
The step's output is { "response": { "status": 200, "result": { "id": "..." } } }. Take result.id and write it to the record that caused the message — an email_id column on the order — with an Edit Record step. Later, "did they get it?" is a search on the messages page by that id.
Branch on response.status. A 402 is the month's allowance gone; a 403 with validation_error is a from address on a domain that is not verified. Both are worth surfacing in the API's own response rather than swallowing, so the front end knows the receipt did not go.
Table triggers and not sending twice
Xano's Triggers run a stack when a record is added. A stack that sends from a trigger should add a third header, Idempotency-Key, set to something unique to the record — "order-" ~ $input.id — so a retried run returns the original message instead of sending another.
Before it works
Add mail.yourapp.com in Pigeon Atlas, publish the DKIM, SPF and MX records it shows, and wait for the ticks beside them. Sending from a domain that has not resolved is refused with a message naming the missing record. Use a subdomain rather than the bare domain, so the app's transactional mail keeps a reputation of its own.
A thousand a month free, no card
Enough to verify a domain and run a small app from it.