Send email from PHP
Five steps: install Guzzle, create a test key, send to the sandbox, read the log, then verify a domain so you can send to anyone. Using Laravel? The Laravel quickstart uses the Http facade instead.
1. Install
This guide needs no SDK. The API is plain JSON over HTTPS; any HTTP client works, and Guzzle is the one most PHP projects already have.
composer require guzzlehttp/guzzle2. Create an API key
Sign in, open API keys in the dashboard and create a test key. It starts with av_test_. Export it so the script can read it:
export AVELTO_API_KEY=av_test_...3. Send your first email
Every request carries the key as a bearer token. Guzzle throws on a non-2xx status; the catch reads the code and message from the error envelope.
<?php// send.phprequire "vendor/autoload.php";use GuzzleHttp\Client;use GuzzleHttp\Exception\ClientException;$client = new Client([ "base_uri" => "https://api.avelto.dev", "headers" => ["Authorization" => "Bearer " . getenv("AVELTO_API_KEY")],]);try { $res = $client->post("/v1/emails", ["json" => [ "from" => "you@sandbox.avelto.dev", "to" => "delivered@sandbox.avelto.dev", "subject" => "Hello from Avelto", "text" => "It works.", ]]);} catch (ClientException $e) { $error = json_decode((string) $e->getResponse()->getBody(), true)["error"]; fwrite(STDERR, $e->getResponse()->getStatusCode() . " {$error["code"]}: {$error["message"]}\n"); exit(1);}$email = json_decode((string) $res->getBody(), true);echo $email["id"], "\n"; // "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10"php send.phpThe API answers 201 Created with the email id:
HTTP/1.1 201 CreatedContent-Type: application/json{ "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" }4. Check the log
Fetch the email by id with the same client. status moves from queued to sent to delivered, and events records each step: email.queued, email.sent, email.delivered.
$email = json_decode((string) $client->get("/v1/emails/{$email["id"]}")->getBody(), true);echo $email["status"], "\n"; // "queued", then "sent", then "delivered"foreach ($email["events"] as $e) { echo $e["type"], " ", $e["occurred_at"], "\n";}{ "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10", "mode": "test", "from": "you@sandbox.avelto.dev", "to": ["delivered@sandbox.avelto.dev"], "subject": "Hello from Avelto", "status": "delivered", "events": [ { "id": "e1f0c3a4-8b2d-4c6e-9a1f-5d7b3e2c8a90", "type": "email.queued", "payload": {}, "occurred_at": "2026-09-17T10:12:04.000Z" }, { "id": "a7c2e9d1-3f4b-4a8e-b6c0-2d9e1f7b5c34", "type": "email.sent", "payload": { "test": true, "ses_message_id": "test-9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" }, "occurred_at": "2026-09-17T10:12:06.000Z" }, { "id": "c4b8d2f6-7e1a-4d3c-8f9b-6a2e0c5d1b78", "type": "email.delivered", "payload": { "test": true, "recipients": ["delivered@sandbox.avelto.dev"] }, "occurred_at": "2026-09-17T10:12:06.000Z" } ]}5. Verify a domain
Add a domain, publish the DNS records it prints (an ownership TXT, an SPF TXT and a DMARC TXT, then three DKIM CNAMEs once the ownership record is found), then poll GET /v1/domains/:id until status is verified. The GET re-checks DNS on every call. Use a subdomain such as mail.acme.com.
<?php// verify_domain.php (same $client as send.php)$res = $client->post("/v1/domains", ["json" => ["name" => "mail.acme.com"]]);$domain = json_decode((string) $res->getBody(), true);$show = function (array $d): void { foreach ($d["dns_records"] as $r) { echo "{$r["type"]}\t{$r["name"]}\t{$r["value"]}\t({$r["purpose"]})\n"; }};$show($domain); // the ownership record first, with SPF and DMARC// Publish the records, then poll. GET re-checks DNS on every call.// Once the ownership record is found, the DKIM records appear: publish those too.while ($domain["status"] === "pending") { sleep(30); $latest = json_decode((string) $client->get("/v1/domains/{$domain["id"]}")->getBody(), true); if ($latest["ownership_verified"] && !$domain["ownership_verified"]) { $show($latest); } $domain = $latest;}echo $domain["status"], "\n"; // "verified" or "failed"php verify_domain.phpOnce the domain is verified, switch AVELTO_API_KEY to a live key (av_live_) and change from to an address on it, such as hello@mail.acme.com. Nothing else changes.
Next
- Send email: every field, attachments, tags, scheduling and idempotency.
- Webhooks: get events pushed to your app.
- Test mode: test keys, the sandbox sender and the simulator addresses.