📧 Módulo 7 - Correos Automáticos

PHPMailer: Envío de correos y recuperación de contraseña

¿Quieres automatizar el envío de correos desde tu aplicación PHP? En esta guía aprenderás a configurar PHPMailer para confirmaciones, notificaciones y recuperación de contraseña con código seguro.

⏱️ ~10 min de lectura 🎯 Nivel: Intermedio 🔐 Con tokens seguros

Automatizar el envío de correos es esencial para cualquier aplicación web moderna. En esta guía aprenderás a configurar PHPMailer, una biblioteca robusta y segura para enviar emails desde PHP.

Configuración de PHPMailer en PHP

Primero, instala PHPMailer usando Composer (recomendado) o descárgalo desde GitHub:

📦 Instalación con Composer
composer require phpmailer/phpmailer

Luego, incluye PHPMailer en tu script:

🔌 Incluir PHPMailer en tu proyecto
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';
?>
⚠️ Nota importante

Nunca hardcodees credenciales de email en producción. Usa variables de entorno o un archivo de configuración seguro.

Enviar correos de confirmación y notificaciones

Ejemplo básico para enviar un correo de bienvenida:

📤 Ejemplo: Correo de bienvenida
$mail = new PHPMailer(true);

try {
    // Configuración del servidor SMTP
    $mail->isSMTP();
    $mail->Host       = 'smtp.gmail.com';
    $mail->SMTPAuth   = true;
    $mail->Username   = getenv('SMTP_USER'); // Usar variables de entorno
    $mail->Password   = getenv('SMTP_PASS');
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port       = 587;
    $mail->CharSet    = 'UTF-8';

    // Destinatarios
    $mail->setFrom('no-reply@tudominio.com', 'Tu Sitio');
    $mail->addAddress('cliente@email.com', 'Cliente');

    // Contenido del correo
    $mail->isHTML(true);
    $mail->Subject = 'Bienvenido a nuestra plataforma';
    $mail->Body    = '
        <h1>¡Hola, bienvenido!</h1>
        <p>Gracias por registrarte en nuestra plataforma.</p>
        <p>Ya puedes comenzar a usar tu cuenta.</p>
        <a href="https://tudominio.com/login" 
           style="background:#2563eb;color:white;padding:10px 20px;
                  text-decoration:none;border-radius:5px">
            Iniciar sesión
        </a>
    ';
    $mail->AltBody = 'Hola, bienvenido. Gracias por registrarte. Visita: https://tudominio.com/login';
    
    $mail->send();
    echo 'Correo enviado exitosamente';
} catch (Exception $e) {
    error_log("Error al enviar correo: {$mail->ErrorInfo}");
    echo "No se pudo enviar el mensaje. Intenta más tarde.";
}

Resultado: Un correo HTML profesional con fallback de texto plano para clientes que no soportan HTML.

Implementación de recuperación de contraseña por correo

Este es uno de los usos más importantes de PHPMailer. Aquí el flujo completo:

🔐 Ejemplo: Generar token y enviar enlace
// 1. Generar token seguro (50 bytes = 100 caracteres hex)
$token = bin2hex(random_bytes(50));
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));

// 2. Guardar en base de datos (tabla password_resets)
$stmt = $conn->prepare("INSERT INTO password_resets (email, token, expires_at) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $email, $token, $expires);
$stmt->execute();

// 3. Crear enlace de recuperación
$enlace = "https://tudominio.com/recuperar.php?token=" . $token;

// 4. Configurar y enviar correo
$mail = new PHPMailer(true);
$mail->isSMTP();
// ... configuración SMTP ...

$mail->setFrom('no-reply@tudominio.com', 'Tu Sitio');
$mail->addAddress($email);
$mail->Subject = 'Recuperación de contraseña';
$mail->isHTML(true);
$mail->Body = "
    <h2>Recuperación de contraseña</h2>
    <p>Haz clic en el siguiente enlace para restablecer tu contraseña:</p>
    <p><a href='$enlace' style='background:#2563eb;color:white;
          padding:12px 24px;text-decoration:none;border-radius:5px;
          display:inline-block'>Restablecer contraseña</a></p>
    <p style='font-size:0.9rem;color:#64748b'>
        Este enlace expira en 1 hora. Si no solicitaste esto, ignora este correo.
    </p>
";
$mail->AltBody = "Recuperación de contraseña. Enlace: $enlace (expira en 1 hora)";

$mail->send();
✅ Buenas prácticas para tokens

• Usa random_bytes() para tokens criptográficamente seguros
• Establece una expiración corta (30-60 minutos)
• Invalida el token después de usarlo
• Registra intentos para detectar abusos

Validar el token en recuperar.php

Cuando el usuario hace clic en el enlace, valida el token antes de permitir el cambio:

🔍 Ejemplo: Validar token y actualizar contraseña
// recuperar.php
$token = $_GET['token'] ?? '';

// 1. Buscar token válido en la base de datos
$stmt = $conn->prepare("
    SELECT email, expires_at FROM password_resets 
    WHERE token = ? AND used = 0 AND expires_at > NOW()
    LIMIT 1
");
$stmt->bind_param("s", $token);
$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows === 0) {
    die("Enlace inválido o expirado");
}

$row = $result->fetch_assoc();
$email = $row['email'];

// 2. Mostrar formulario para nueva contraseña (HTML)
// ... formulario con campos password y confirm_password ...

// 3. Procesar el nuevo password
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $new_password = $_POST['password'];
    $confirm = $_POST['confirm_password'];
    
    // Validar fortaleza del password
    if (strlen($new_password) < 8 || $new_password !== $confirm) {
        $error = "La contraseña debe tener al menos 8 caracteres y coincidir";
    } else {
        // Hash seguro y actualizar usuario
        $hash = password_hash($new_password, PASSWORD_DEFAULT);
        $stmt = $conn->prepare("UPDATE users SET password = ? WHERE email = ?");
        $stmt->bind_param("ss", $hash, $email);
        $stmt->execute();
        
        // Marcar token como usado
        $stmt = $conn->prepare("UPDATE password_resets SET used = 1 WHERE token = ?");
        $stmt->bind_param("s", $token);
        $stmt->execute();
        
        echo "Contraseña actualizada exitosamente";
    }
}

Seguridad y buenas prácticas

✅ Usa HTTPS siempre

Los tokens viajan por URL. Sin HTTPS, pueden ser interceptados.

✅ Rate limiting

Limita solicitudes de recuperación por email/IP para prevenir abusos.

✅ Logging de auditoría

Registra intentos de recuperación para detectar patrones sospechosos.

⚠️ Nunca muestres si un email existe

En el formulario de "Olvidé mi contraseña", siempre muestra el mismo mensaje ("Si el email existe, recibirás instrucciones") para no revelar qué usuarios están registrados en tu sistema.

¿Qué sigue después de PHPMailer?

Este es el séptimo paso del desarrollo full-stack. En el siguiente módulo, integrarás todo lo aprendido en un sistema de autenticación completo con dashboard.

📚 ¿Quieres la versión completa del libro?

Esta guía es una versión resumida del libro "Programación Full-Stack", que incluye ejercicios resueltos, códigos listos y explicaciones profundas de seguridad en correos.

Comprar en Amazon →

¿Tienes dudas sobre PHPMailer, tokens o seguridad en correos? ¡Déjalas en los comentarios! 👇