Skip to content
Docs

Tutorial

Send email from any PHP app with the emailit/emailit-php SDK, plain cURL or PHPMailer over SMTP, and verify Emailit webhook signatures.

Updated Oct 1, 2026

This guide covers sending email from a PHP application without a framework: with the official emailit/emailit-php SDK, with plain cURL, or over SMTP with PHPMailer. It also shows a webhook endpoint that verifies signatures. Using Laravel? Follow the Laravel guide instead.

Prerequisites

  • PHP 8.1 or later and Composer.
  • A verified sending domain, for example acme.com.
  • An API key. A Sending Only key restricted to your domain is enough.
  • Until your workspace has production access, you can only send to the account emails of workspace members.

Install the SDK

Terminal
composer require emailit/emailit-php

The SDK uses Guzzle for HTTP, which Composer installs for you.

Configure your API key

Set the key as an environment variable in your web server, PHP-FPM pool or hosting panel, and read it at runtime:

Terminal
export EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
PHP
$apiKey = getenv('EMAILIT_API_KEY');

If you load a .env file with a package such as vlucas/phpdotenv, keep that file outside the web root and out of version control.

Send an email

send.php
require __DIR__.'/vendor/autoload.php';

$emailit = Emailit::client(getenv('EMAILIT_API_KEY'));

$email = $emailit->emails()->send([
    'from'    => 'Acme <hello@acme.com>',
    'to'      => 'ada@example.com',
    'subject' => 'Your receipt from Acme',
    'html'    => '<p>Thanks for your order.</p>',
    'text'    => 'Thanks for your order.',
]);

echo $email->id; // em_…

The same call accepts cc, bcc, reply_to, attachments, template with variables, scheduled_at and tracking. To attach a file, base64-encode it:

PHP
$email = $emailit->emails()->send([
    'from'        => 'Acme Billing <billing@acme.com>',
    'to'          => 'ada@example.com',
    'subject'     => 'Invoice INV-1042',
    'html'        => '<p>Your invoice is attached.</p>',
    'attachments' => [[
        'filename'     => 'INV-1042.pdf',
        'content'      => base64_encode(file_get_contents(__DIR__.'/INV-1042.pdf')),
        'content_type' => 'application/pdf',
    ]],
]);

See Send an email for every field and Attachments for limits.

Handle errors

PHP
use Emailit\Exceptions\ApiErrorException;
use Emailit\Exceptions\AuthenticationException;
use Emailit\Exceptions\RateLimitException;
use Emailit\Exceptions\UnprocessableEntityException;

try {
    $emailit->emails()->send($message);
} catch (AuthenticationException $e) {
    // 401: the API key is missing or invalid
} catch (RateLimitException $e) {
    // 429: wait and retry
} catch (UnprocessableEntityException $e) {
    // 422: for example, the from domain isn't verified
} catch (ApiErrorException $e) {
    error_log('Emailit error '.$e->getHttpStatus().': '.$e->getMessage());
}

Send without the SDK

If you can’t use Composer, call the API with cURL:

send-curl.php
$ch = curl_init('https://api.emailit.com/v2/emails');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer '.getenv('EMAILIT_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'from'    => 'Acme <hello@acme.com>',
        'to'      => 'ada@example.com',
        'subject' => 'Your receipt from Acme',
        'html'    => '<p>Thanks for your order.</p>',
    ]),
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status >= 400) {
    throw new RuntimeException("Emailit returned $status: $body");
}

$email = json_decode($body, true); // $email['id'] is the em_ ID

Send with SMTP instead

Any PHP mail library that supports SMTP works. With PHPMailer (composer require phpmailer/phpmailer):

send-smtp.php
use PHPMailer\PHPMailer\PHPMailer;

require __DIR__.'/vendor/autoload.php';

$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host       = 'smtp.emailit.com';
$mail->Port       = 587;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->SMTPAuth   = true;
$mail->Username   = 'emailit';
$mail->Password   = getenv('EMAILIT_API_KEY');

$mail->setFrom('hello@acme.com', 'Acme');
$mail->addAddress('ada@example.com');
$mail->Subject = 'Your receipt from Acme';
$mail->isHTML(true);
$mail->Body    = '<p>Thanks for your order.</p>';
$mail->AltBody = 'Thanks for your order.';

$mail->send();

With Symfony Mailer, use the DSN smtp://emailit:API_KEY@smtp.emailit.com:587. For implicit TLS use port 465 (PHPMailer::ENCRYPTION_SMTPS); if your host blocks 587, try 2525 or 2587. See SMTP settings.

Receive webhooks

Create a webhook that points to your endpoint and store its signing secret in EMAILIT_WEBHOOK_SECRET. Read the raw body from php://input and verify it before you trust it:

webhook.php
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_EMAILIT_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_EMAILIT_TIMESTAMP'] ?? '';
$secret    = getenv('EMAILIT_WEBHOOK_SECRET');

$expected = hash_hmac('sha256', $timestamp.'.'.$payload, $secret);

if (abs(time() - (int) $timestamp) > 300 || ! hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

// The body is a JSON array of up to 100 events.
foreach (json_decode($payload, true) as $event) {
    if ($event['type'] === 'email.bounced') {
        $address = $event['data']['object']['to'];
        // Stop emailing $address.
    }
}

http_response_code(200);

Respond within 30 seconds with a 2xx; anything else is retried. See Request signature.

Production tips

  • Reuse one client per request or worker instead of creating one per email.
  • Send in the background. For bulk sends, push messages onto a queue and send from a worker, so pages stay fast and you stay under your sending limits (2 emails per second by default).
  • Make retries safe. If you retry after a timeout, send an Idempotency-Key header with cURL so the email isn’t sent twice. See Idempotency.
  • Keep keys out of the web root. Never commit keys or .env files, and use a separate key per app.

Next steps

Mail transport and facade for Laravel apps.
Route WordPress mail through Emailit SMTP.
Templates, scheduling and tracking.
Every event and its payload.

Was this page helpful?

Thanks for the feedback.

Thanks, we read every message.