Tutorial
Enviar emails con PHP
Envía emails desde cualquier aplicación PHP con el SDK emailit/emailit-php, con cURL sin más o con PHPMailer por SMTP, y verifica las firmas de los webhooks de Emailit.
En esta guía se explica cómo enviar emails desde una aplicación PHP sin framework: con el SDK oficial emailit/emailit-php, con cURL sin más o por SMTP con PHPMailer. También se incluye un endpoint de webhook que verifica las firmas. ¿Usas Laravel? Sigue en su lugar la guía de Laravel.
Requisitos previos
- PHP 8.1 o posterior y Composer.
- Un dominio de envío verificado, por ejemplo
acme.com. - Una clave de API. Basta con una clave Sending Only limitada a tu dominio.
- Hasta que tu espacio de trabajo tenga acceso de producción, solo puedes enviar a las direcciones de email de las cuentas de los miembros del espacio de trabajo.
Instalar el SDK
composer require emailit/emailit-phpEl SDK usa Guzzle para HTTP, que Composer instala por ti.
Configurar la clave de API
Define la clave como variable de entorno en tu servidor web, en el pool de PHP-FPM o en el panel de tu hosting, y léela en tiempo de ejecución:
export EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••$apiKey = getenv('EMAILIT_API_KEY');Si cargas un archivo .env con un paquete como vlucas/phpdotenv, guarda ese archivo fuera de la raíz web y fuera del control de versiones.
Enviar un email
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_…La misma llamada acepta cc, bcc, reply_to, attachments, template con variables, scheduled_at y tracking. Para adjuntar un archivo, codifícalo en base64:
$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',
]],
]);Para ver todos los campos, consulta Enviar un email, y para los límites, Adjuntos.
Gestionar los errores
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());
}Enviar sin el SDK
Si no puedes usar Composer, llama a la API con cURL:
$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_ IDEnviar por SMTP
Funciona cualquier biblioteca de correo de PHP que admita SMTP. Con PHPMailer (composer require phpmailer/phpmailer):
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();Con Symfony Mailer, usa el DSN smtp://emailit:API_KEY@smtp.emailit.com:587. Para TLS implícito, usa el puerto 465 (PHPMailer::ENCRYPTION_SMTPS); si tu proveedor de hosting bloquea el 587, prueba con el 2525 o el 2587. Consulta Configuración SMTP.
Recibir webhooks
Crea un webhook que apunte a tu endpoint y guarda su secreto de firma en EMAILIT_WEBHOOK_SECRET. Lee el cuerpo en bruto de php://input y verifícalo antes de fiarte de él:
$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);Responde en menos de 30 segundos con un 2xx; cualquier otra respuesta se reintenta. Consulta Firma de las peticiones.
Consejos para producción
- Reutiliza un único cliente por petición o por worker en lugar de crear uno por email.
- Envía en segundo plano. Para los envíos masivos, pon los mensajes en una cola y envíalos desde un worker, para que las páginas sigan siendo rápidas y no superes tus límites de envío (2 emails por segundo por defecto).
- Haz que los reintentos sean seguros. Si reintentas después de que se agote el tiempo de espera, envía una cabecera
Idempotency-Keycon cURL para que el email no se envíe dos veces. Consulta Idempotencia. - Mantén las claves fuera de la raíz web. No subas nunca claves ni archivos
.enval repositorio, y usa una clave distinta para cada aplicación.