Enviar email en PHP: mail(), PHPMailer con SMTP y adjuntos

Enviar correos desde PHP parece sencillo al principio. La función mail() existe desde la primera versión del lenguaje. Pero en producción, mail() sola es insuficiente: los correos acaban en spam, no soporta SMTP autenticado, el HTML queda limitado y los adjuntos son un dolor. PHPMailer cubre todo eso sin añadir complejidad innecesaria.

La función mail() nativa y sus limitaciones

<?php
// Uso básico de mail()
$para     = '[email protected]';
$asunto   = 'Prueba de email';
$mensaje  = 'Este es el cuerpo del mensaje.';
$cabeceras = 'From: [email protected]' . "rn" .
             'Reply-To: [email protected]' . "rn" .
             'Content-Type: text/plain; charset=UTF-8';

$enviado = mail($para, $asunto, $mensaje, $cabeceras);
echo $enviado ? 'Enviado' : 'Error al enviar';

Los problemas de mail(): depende del MTA del servidor (sendmail, postfix), no soporta SMTP con credenciales, no tiene reintentos, los headers hay que escribirlos a mano y es difícil de depurar. Para proyectos reales, usa PHPMailer.

Instalar PHPMailer

composer require phpmailer/phpmailer

Enviar con SMTP de Gmail

<?php
use PHPMailerPHPMailerPHPMailer;
use PHPMailerPHPMailerSMTP;
use PHPMailerPHPMailerException;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);  // true activa excepciones

try {
    // Configuración SMTP
    $mail->isSMTP();
    $mail->Host       = 'smtp.gmail.com';
    $mail->SMTPAuth   = true;
    $mail->Username   = '[email protected]';
    $mail->Password   = 'app_password_aqui';   // contraseña de aplicación, no la de Gmail
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port       = 587;
    $mail->CharSet    = 'UTF-8';

    // Remitente y destinatario
    $mail->setFrom('[email protected]', 'Mi Aplicación');
    $mail->addAddress('[email protected]', 'Ana García');
    $mail->addCC('[email protected]');
    $mail->addBCC('[email protected]');

    // Contenido
    $mail->isHTML(true);
    $mail->Subject = 'Confirmación de pedido #12345';
    $mail->Body    = '<h1>Pedido confirmado</h1><p>Hola Ana, tu pedido está en camino.</p>';
    $mail->AltBody = 'Hola Ana, tu pedido está en camino.';  // texto plano alternativo

    $mail->send();
    echo 'Email enviado correctamente';
} catch (Exception $e) {
    echo "Error: {$mail->ErrorInfo}";
}

Enviar con SendGrid (alternativa a Gmail para producción)

<?php
$mail->isSMTP();
$mail->Host       = 'smtp.sendgrid.net';
$mail->SMTPAuth   = true;
$mail->Username   = 'apikey';                     // literal: "apikey"
$mail->Password   = 'SG.TU_CLAVE_API_AQUI';
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port       = 587;

Email HTML con texto alternativo

Los clientes de email que no soportan HTML mostrarán el AltBody. Siempre debes proporcionarlo:

<?php
$mail->isHTML(true);
$mail->Subject = 'Tu factura de septiembre';
$mail->Body    = <<<HTML
<!DOCTYPE html>
<html>
<body style="font-family: Arial, sans-serif; color: #333;">
  <h2 style="color: #2563eb;">Factura #2026-09</h2>
  <p>Hola <strong>Ana</strong>, adjuntamos tu factura de septiembre.</p>
  <table border="0" cellpadding="8" style="border-collapse: collapse;">
    <tr><td>Servicio:</td><td><strong>Plan Pro</strong></td></tr>
    <tr><td>Importe:</td><td><strong>49,00 €</strong></td></tr>
  </table>
  <p style="color: #666; font-size: 0.9em;">Este email fue enviado automáticamente.</p>
</body>
</html>
HTML;
$mail->AltBody = "Hola Ana, adjuntamos tu factura de septiembre.nPlan Pro: 49,00 €";

Adjuntos con addAttachment()

<?php
// Adjuntar un fichero del servidor
$mail->addAttachment('/var/facturas/factura_2026_09.pdf', 'Factura_Septiembre.pdf');

// Adjuntar desde string (sin escribir en disco)
$pdfContent = generarFacturaPDF($pedido);
$mail->addStringAttachment($pdfContent, 'Factura.pdf', PHPMailer::ENCODING_BASE64, 'application/pdf');

// Adjuntar imagen inline (para usarla en el Body con cid:)
$cid = $mail->addEmbeddedImage('/ruta/logo.png', 'logo_cid');
$mail->Body = '<img src="cid:logo_cid"><p>Contenido...</p>';

// Múltiples adjuntos
foreach ($archivos as $ruta => $nombre) {
    $mail->addAttachment($ruta, $nombre);
}

Configurar From y ReplyTo correctamente

Una de las causas más frecuentes de que los emails caigan en spam es que el From no coincida con el dominio del servidor de envío:

<?php
// BIEN: From del mismo dominio que el servidor SMTP
$mail->setFrom('[email protected]', 'Mi Aplicación');
$mail->addReplyTo('[email protected]', 'Soporte');

// MAL: From de un dominio diferente al servidor SMTP
// Si envías desde smtp.gmail.com pero pones From: [email protected]
// Gmail lo reescribirá a [email protected]

// Para envíos desde dominio propio: usa un relay SMTP de tu proveedor
// o servicios como SendGrid, Postmark, Mailgun con SPF/DKIM configurados

Depuración y niveles de debug

<?php
// Activa la salida de debug (solo en desarrollo)
$mail->SMTPDebug = SMTP::DEBUG_SERVER;  // muestra la conversación SMTP completa
// Niveles: DEBUG_OFF (0), DEBUG_CLIENT (1), DEBUG_SERVER (2), DEBUG_CONNECTION (3)

// Redirigir el debug a un log en lugar de pantalla
$mail->Debugoutput = function(string $str, int $level) {
    file_put_contents('/var/log/phpmailer.log', date('Y-m-d H:i:s') . ' ' . $str . "n", FILE_APPEND);
};

Clase auxiliar para centralizar la configuración

<?php
class Mailer {
    private PHPMailer $mail;

    public function __construct() {
        $this->mail = new PHPMailer(true);
        $this->mail->isSMTP();
        $this->mail->Host       = $_ENV['SMTP_HOST'];
        $this->mail->SMTPAuth   = true;
        $this->mail->Username   = $_ENV['SMTP_USER'];
        $this->mail->Password   = $_ENV['SMTP_PASS'];
        $this->mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
        $this->mail->Port       = (int)$_ENV['SMTP_PORT'];
        $this->mail->CharSet    = 'UTF-8';
        $this->mail->setFrom($_ENV['MAIL_FROM'], $_ENV['MAIL_FROM_NAME']);
    }

    public function enviar(string $para, string $nombre, string $asunto, string $html, string $texto = ''): void {
        $this->mail->clearAddresses();
        $this->mail->clearAttachments();
        $this->mail->addAddress($para, $nombre);
        $this->mail->isHTML(true);
        $this->mail->Subject = $asunto;
        $this->mail->Body    = $html;
        $this->mail->AltBody = $texto ?: strip_tags($html);
        $this->mail->send();
    }
}

$mailer = new Mailer();
$mailer->enviar('[email protected]', 'Ana García', 'Bienvenida', '<p>Hola Ana</p>');

Problemas comunes y soluciones

  • Error «Could not authenticate»: Gmail requiere contraseñas de aplicación (no la contraseña de tu cuenta). Actívala en Configuración > Seguridad > Contraseñas de aplicación.
  • Emails en spam: configura SPF, DKIM y DMARC en tu DNS. PHPMailer no puede solucionar problemas de reputación de dominio.
  • Caracteres raros en el asunto: PHPMailer codifica automáticamente el Subject en UTF-8. No uses =?UTF-8? manual.
  • Adjunto con nombre en español: PHPMailer lo codifica correctamente; no necesitas manipular el nombre manualmente.
  • mail() no funciona en hosting compartido: muchos hostings la deshabilitan o limitan. Usa siempre SMTP explícito.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP