Quickstart: send your first email
From zero to your first delivered email: verify a domain, create a key and make a POST /emails request.
#1. Verify a domain
Postal only sends from domains you have proven you control. In the dashboard, under Domains, add your domain (for example, acmestore.com) and create the records it shows at your DNS provider: a DKIM TXT record, plus an MX and a TXT record for the Return-Path. Verification usually takes a few minutes. Details in Domains and DNS.
#2. Create an API key
Under API keys, create a sending-only key and copy the token (it is shown once). Keep it in an environment variable:
export POSTAL_API_KEY="ps_your_key"
#3. Send your first email
Replace the sender with an address on your verified domain and the recipient with your own email:
cURL
curl -X POST https://api.postal.so/emails \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme Store <[email protected]>",
"to": ["[email protected]"],
"subject": "Your order #10482 is out for delivery",
"html": "<p>Order <b>#10482</b> is on its way.</p>"
}'
Node.js
const response = await fetch('https://api.postal.so/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'Acme Store <[email protected]>',
to: ['[email protected]'],
subject: 'Your order #10482 is out for delivery',
html: '<p>Order <b>#10482</b> is on its way.</p>',
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/emails",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"from": "Acme Store <[email protected]>",
"to": ["[email protected]"],
"subject": "Your order #10482 is out for delivery",
"html": "<p>Order <b>#10482</b> is on its way.</p>",
},
timeout=10,
)
data = response.json()
PHP
<?php
$ch = curl_init('https://api.postal.so/emails');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('POSTAL_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'from' => 'Acme Store <[email protected]>',
'to' => ['[email protected]'],
'subject' => 'Your order #10482 is out for delivery',
'html' => '<p>Order <b>#10482</b> is on its way.</p>',
]),
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status === 200 ? $data['id'] : $data['message'];
Laravel
// config/services.php
'postal' => [
'key' => env('POSTAL_API_KEY'),
],
// Anywhere in your application
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.postal.key'))
->withHeaders(['Idempotency-Key' => 'order-10482-shipped'])
->post('https://api.postal.so/emails', [
'from' => 'Acme Store <[email protected]>',
'to' => ['[email protected]'],
'subject' => 'Your order #10482 is out for delivery',
'html' => view('emails.order-shipped', ['order' => $order])->render(),
])
->throw();
$emailId = $response->json('id');
The response carries the email ID. If something is wrong, you get an error with the reason, such as 422 validation_error or 403 for an unverified domain.
{
"id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b"
}
Laravel with the framework Mail
Prefer to keep using Mail::to() and Mailables? Configure the SMTP mailer with your key and nothing else changes in your code.
#4. Track the delivery
Fetch the email to see its status and events (sent, delivered, opened…):
cURL
curl https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('emails/019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
In production, instead of polling, register a webhook and receive each event as it happens. To read the status through the API, the key needs full access.
#Next steps
- Avoid duplicate sends on retries with the
Idempotency-Key. - Build the content in the dashboard and send it with templates and variables.
- Send up to 100 emails per request with batch sending.
- Test your integration and webhooks without really sending, using the test addresses (
[email protected],[email protected]…). - Learn the limits of your plan.