Quickstart: PHP
No Composer, no Guzzle, no curl extension required — PHP's built-in HTTP stream wrapper is enough to send and verify.
Facts verified 2026-07-17 — corrections: hello@emailfast.dev
1. Send
The API is plain JSON over HTTPS with bearer auth. file_get_contents with a stream context does it in stock PHP — nothing to install. (If you prefer the curl extension or a generated PHP SDK, both speak to the same two endpoints — see the docs.)
<?php
$apiKey = getenv("EMAILFAST_API_KEY"); // ef_sandbox_… works everywhere
$body = json_encode([
"to" => "ada@example.com",
"subject" => "Welcome to the ledger",
"html" => "<h1>Hi {{name}}</h1><p>Your account is ready.</p>",
"data" => ["name" => "Ada"],
"idempotency_key" => "welcome-ada-1", // retry-safe: same key can never double-send
]);
$ctx = stream_context_create(["http" => [
"method" => "POST",
"header" => "Authorization: Bearer $apiKey\r\nContent-Type: application/json",
"content" => $body,
]]);
$msg = json_decode(file_get_contents("https://api.emailfast.dev/v1/emails", false, $ctx), true);
echo $msg["id"], " ", $msg["status"], PHP_EOL; // msg_… queueda 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send. That is what the idempotency_key buys you: put this call behind any retry logic and a timeout after the server committed the send still can't produce a duplicate.
2. Watch it happen
<?php
$ctx = stream_context_create(["http" => [
"header" => "Authorization: Bearer " . getenv("EMAILFAST_API_KEY"),
]]);
$timeline = json_decode(
file_get_contents("https://api.emailfast.dev/v1/messages/" . $msg["id"], false, $ctx),
true
);
foreach ($timeline["events"] as $e) {
echo $e["type"], PHP_EOL; // admitted → rendered → (sandbox-)delivered …
}With a sandbox key the message lands in your hosted capture inbox — same pipeline, nothing sent: sandbox keys (ef_sandbox_…) that run the real pipeline dry: real validation, real rendering, real events, a hosted capture inbox — and no email leaves.
Notes for production PHP
- Set
ignore_errors => truein the context if you want to read the JSON error body on a 4xx instead of a PHP warning. - The same two endpoints —
POST /v1/emails,GET /v1/messages/:id— are all a queue worker or a WordPress hook needs.