Skip to content

Correo con PHPMailer (SMTP) ​

La función mail() de PHP suele terminar en spam o directamente no funciona. PHPMailer envía a través de un servidor SMTP real (el de tu hosting, Gmail, Brevo, Mailgun...), con autenticación y cifrado, así que tus correos sí llegan.

Instalación ​

bash
composer require phpmailer/phpmailer

Esto crea la carpeta vendor/. Si tu hosting no tiene Composer, instálalo en tu computadora y sube la carpeta vendor/ completa por FTP.

Variables de entorno ​

env
MAIL_HOST=smtp.tudominio.com
MAIL_PORT=587
MAIL_USER=no-responder@tudominio.com
MAIL_PASS=contraseña-del-buzon
MAIL_SECURE=tls          # tls (puerto 587) o ssl (puerto 465)
MAIL_FROM=no-responder@tudominio.com
MAIL_FROM_NAME="Mi Negocio"
Datos SMTP de servicios comunes
ServicioHostPuertoSeguridad
cPanel / hostingmail.tudominio.com465 / 587ssl / tls
Gmail (con contraseña de aplicación)smtp.gmail.com587tls
Outlook / Microsoft 365smtp.office365.com587tls
Brevo (ex Sendinblue)smtp-relay.brevo.com587tls

Para Gmail necesitas activar la verificación en dos pasos y crear una contraseña de aplicación en tu cuenta de Google.

La función reutilizable ​

php
<?php
require_once __DIR__ . '/env.php';
require_once dirname(__DIR__) . '/vendor/autoload.php';

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

/**
 * Envía un correo HTML.
 *
 * @param string|array $para    'ana@correo.com' o ['ana@correo.com' => 'Ana López', ...]
 * @param string       $asunto
 * @param string       $html    Cuerpo en HTML
 * @param array        $opciones ['texto' => ..., 'responder_a' => ..., 'adjuntos' => [ruta, ...], 'cc' => [...]]
 * @return bool true si se envió
 */
function enviar_correo(string|array $para, string $asunto, string $html, array $opciones = []): bool
{
    $mail = new PHPMailer(true); // true = lanza excepciones

    try {
        // Servidor
        $mail->isSMTP();
        $mail->Host       = env('MAIL_HOST');
        $mail->SMTPAuth   = true;
        $mail->Username   = env('MAIL_USER');
        $mail->Password   = env('MAIL_PASS');
        $mail->SMTPSecure = env('MAIL_SECURE', 'tls') === 'ssl'
            ? PHPMailer::ENCRYPTION_SMTPS
            : PHPMailer::ENCRYPTION_STARTTLS;
        $mail->Port       = (int) env('MAIL_PORT', 587);
        $mail->CharSet    = 'UTF-8';
        // $mail->SMTPDebug = 2; // ← descomenta para ver el diálogo SMTP al depurar

        // Remitente (debe ser del mismo dominio del buzón para no caer en spam)
        $mail->setFrom(env('MAIL_FROM'), env('MAIL_FROM_NAME', ''));

        // Destinatarios
        foreach ((array) $para as $clave => $valor) {
            is_int($clave) ? $mail->addAddress($valor) : $mail->addAddress($clave, $valor);
        }
        foreach ($opciones['cc'] ?? [] as $cc) {
            $mail->addCC($cc);
        }
        if (!empty($opciones['responder_a'])) {
            $mail->addReplyTo($opciones['responder_a']);
        }
        foreach ($opciones['adjuntos'] ?? [] as $ruta) {
            $mail->addAttachment($ruta);
        }

        // Contenido
        $mail->isHTML(true);
        $mail->Subject = $asunto;
        $mail->Body    = plantilla_correo($asunto, $html);
        $mail->AltBody = $opciones['texto'] ?? trim(strip_tags(str_replace(['<br>', '</p>'], "\n", $html)));

        return $mail->send();
    } catch (Exception $e) {
        error_log('Error al enviar correo: ' . $mail->ErrorInfo);
        return false;
    }
}

/** Envuelve el contenido en un diseño sencillo compatible con clientes de correo */
function plantilla_correo(string $titulo, string $contenido): string
{
    $marca = htmlspecialchars(env('MAIL_FROM_NAME', 'Mi sitio'));
    return <<<HTML
    <!DOCTYPE html>
    <html lang="es"><body style="margin:0;background:#f1f5f9;font-family:Arial,sans-serif">
      <table width="100%" cellpadding="0" cellspacing="0" style="padding:24px 0">
        <tr><td align="center">
          <table width="600" cellpadding="0" cellspacing="0" style="max-width:600px;background:#ffffff;border-radius:12px;overflow:hidden">
            <tr><td style="background:#4f46e5;color:#fff;padding:20px 28px;font-size:20px;font-weight:bold">$marca</td></tr>
            <tr><td style="padding:28px;color:#1e293b;font-size:15px;line-height:1.6">
              <h2 style="margin-top:0">$titulo</h2>
              $contenido
            </td></tr>
            <tr><td style="padding:16px 28px;background:#f8fafc;color:#64748b;font-size:12px">
              Este correo se envió automáticamente, por favor no respondas.
            </td></tr>
          </table>
        </td></tr>
      </table>
    </body></html>
    HTML;
}

Ejemplos de uso ​

php
<?php
require __DIR__ . '/includes/mailer.php';

// Simple
enviar_correo('cliente@correo.com', 'Tu pedido fue recibido', '<p>Gracias por tu compra, pedido <b>#1024</b>.</p>');

// Con nombre, copia, responder-a y adjunto PDF
$ok = enviar_correo(
    ['ana@correo.com' => 'Ana López'],
    'Factura de octubre',
    '<p>Hola Ana, adjuntamos tu factura.</p>',
    [
        'cc'          => ['contabilidad@tudominio.com'],
        'responder_a' => 'ventas@tudominio.com',
        'adjuntos'    => [__DIR__ . '/facturas/F-2026-0045.pdf'],
    ]
);

if (!$ok) {
    echo 'No se pudo enviar el correo. Intenta más tarde.';
}

Que tus correos no lleguen a spam ​

  1. Remitente del mismo dominio: envía desde algo@tudominio.com, nunca desde un Gmail usando el SMTP de tu hosting.
  2. Configura SPF, DKIM y DMARC en el DNS de tu dominio (cPanel → Email Deliverability lo hace casi solo).
  3. Incluye siempre AltBody (versión en texto plano).
  4. Evita asuntos en MAYÚSCULAS y palabras como "GRATIS!!!".
  5. Para envíos masivos (boletines) usa un servicio dedicado (Brevo, Mailchimp, Amazon SES), no tu hosting.

Error común

Poner MAIL_PORT=465 con MAIL_SECURE=tls, o 587 con ssl. El 465 va con ssl y el 587 con tls. Si no coinciden verás "SMTP connect() failed".

Probar sin enviar correos reales

En desarrollo usa Mailtrap o Mailpit: capturan los correos en una bandeja falsa para que veas cómo se ven sin molestar a nadie.

Academia Web — aprende, copia y construye.