El mayor portal de MU Online de Brasil — desde 2003
Tutorial Intermedio Web

Cómo configurar SMTP para la recuperación de cuenta en MU Online

Configura el envío de correo por SMTP en el sitio de tu servidor de MU Online e implementa un flujo seguro de recuperación de contraseña por token, con PHPMailer, expiración y protección contra abuso.

GA Gabriel · Actualizado el 12 jul 2026 · ⏱ 20 min de lectura
Respuesta rápida

Nada frustra más a un jugador que perder el acceso a su cuenta y no tener cómo recuperarla. En servidores de MU Online, la recuperación de contraseña por correo es la solución estándar, pero solo funciona si el sitio consigue enviar correos de forma confiable, y eso depende de un SMTP bien configura

Nada frustra más a un jugador que perder el acceso a su cuenta y no tener cómo recuperarla. En servidores de MU Online, la recuperación de contraseña por correo es la solución estándar, pero solo funciona si el sitio consigue enviar correos de forma confiable, y eso depende de un SMTP bien configurado. Este tutorial cubre las dos mitades del problema: primero hacer que el servidor envíe correos que realmente lleguen a la bandeja de entrada (no al spam), y después construir un flujo de recuperación de contraseña seguro, con token de un solo uso, expiración y protección contra abuso. Los nombres de tablas, columnas y el formato de hash de contraseña aparecen como ejemplo y varían según la versión de tu MuServer, así que confirma cada uno en tu base de datos. Si todavía estás armando la base, mira la guía de cómo crear un servidor de MU Online.

Requisitos previos

El envío de correo junta tres cosas que deben estar alineadas: el sitio, un proveedor SMTP y el DNS de tu dominio.

  • Sitio del servidor en PHP funcionando con acceso a la base donde están las cuentas (MEMB_INFO, por ejemplo).
  • PHP 7.4 o superior con openssl habilitado (necesario para TLS en el SMTP).
  • Composer instalado, para traer la biblioteca PHPMailer.
  • Una cuenta en un proveedor SMTP — puede ser Gmail (pruebas), o un servicio transaccional como Brevo, Mailgun o Amazon SES (producción).
  • Acceso al panel de DNS del dominio, para crear los registros SPF, DKIM y DMARC.
  • Una dirección remitente dedicada, como [email protected].
Proveedor SMTPIndicado paraLímite típico (varía)
Gmail (contraseña de app)Pruebas, servidor pequeño~500 correos/día
Brevo / SendinblueProducción pequeña/mediaPlan gratis ~300/día
MailgunProducción con volumenPago por millar
Amazon SESAlto volumen, bajo costoEscala bajo demanda

Por qué no usar la función mail() nativa

El primer impulso es usar la función mail() de PHP, pero es la causa número uno de correos en la carpeta de spam en servidores privados. Envía directo por el servidor local, casi siempre sin autenticación SMTP, sin SPF alineado y desde una IP con mala reputación. El resultado es previsible: Gmail y Outlook rechazan o marcan como basura. La solución es enviar a través de un servidor SMTP autenticado usando una biblioteca dedicada; PHPMailer es la más consolidada. Se encarga de la negociación TLS, de la autenticación y del formateo correcto de la cabecera.

Instala vía Composer:

composer require phpmailer/phpmailer

Configurando las credenciales SMTP

Mantén las credenciales fuera del código versionado, en un archivo de configuración separado con permiso restringido. Nunca hagas commit de contraseñas de SMTP en el repositorio.

<?php
// config/smtp.php  (fuera de la raíz pública si es posible)
return [
    'host'       => 'smtp-relay.brevo.com', // varía por proveedor
    'porta'      => 587,                     // 587 STARTTLS o 465 SSL
    'seguranca'  => 'tls',                   // 'tls' o 'ssl'
    'usuario'    => 'tu-usuario-smtp',
    'senha'      => 'TU_CONTRASEÑA_O_API_KEY',
    'remetente'  => '[email protected]',
    'nome_de'    => 'Servidor ViciadosMU',
];

Función de envío con PHPMailer

Centraliza el envío en una única función reutilizable. Así, tanto la recuperación de contraseña como cualquier otro correo (bienvenida, aviso de VIP) usan el mismo camino probado.

<?php
// src/Mailer.php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

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

function enviarEmail(string $para, string $assunto, string $corpoHtml): bool
{
    $cfg  = require __DIR__ . '/../config/smtp.php';
    $mail = new PHPMailer(true);

    try {
        $mail->isSMTP();
        $mail->Host       = $cfg['host'];
        $mail->SMTPAuth   = true;
        $mail->Username   = $cfg['usuario'];
        $mail->Password   = $cfg['senha'];
        $mail->SMTPSecure = $cfg['seguranca']; // tls | ssl
        $mail->Port       = $cfg['porta'];
        $mail->CharSet    = 'UTF-8';

        $mail->setFrom($cfg['remetente'], $cfg['nome_de']);
        $mail->addAddress($para);

        $mail->isHTML(true);
        $mail->Subject = $assunto;
        $mail->Body    = $corpoHtml;
        // Versión texto para clientes sin HTML
        $mail->AltBody = strip_tags($corpoHtml);

        $mail->send();
        return true;
    } catch (Exception $e) {
        error_log('[SMTP] Fallo al enviar: ' . $mail->ErrorInfo);
        return false;
    }
}

Pruébalo de forma aislada antes de integrar cualquier cosa. Crea un script rápido que llame a enviarEmail('[email protected]', 'Prueba SMTP', '<b>¡Funcionó!</b>') y confirma la recepción. Si cae en el spam ya en esta prueba, el problema está en el DNS, no en el código.

Configurando SPF, DKIM y DMARC

Esta es la etapa que más impacta en la entrega y la que más se ignora. Estos tres registros DNS le dicen a los servidores de correo que tu dominio autoriza a ese SMTP a enviar en su nombre.

  1. SPF — un registro TXT que lista quién puede enviar por tu dominio. El proveedor SMTP entrega el valor exacto; algo como v=spf1 include:spf.brevo.com ~all.
  2. DKIM — una firma criptográfica. El proveedor genera un par de claves y tú publicas la clave pública en un registro TXT (o CNAME) en el subdominio indicado.
  3. DMARC — una política que dice qué hacer con los correos que fallan SPF/DKIM. Empieza suave: v=DMARC1; p=none; rua=mailto:[email protected], y después endurece a p=quarantine.

Después de publicar, espera la propagación (puede llevar horas) y valida con herramientas de chequeo de SPF/DKIM. Solo pasa a producción cuando los tres pasen.

Preparando la base de datos para el token de recuperación

El flujo de recuperación necesita un lugar donde guardar los tokens temporales. Crea una tabla dedicada; no la mezcles con la tabla de cuentas del juego.

-- Ejemplo (varía por versión): tabla de tokens de recuperación
CREATE TABLE WEB_PASSWORD_RESET (
    id          INT IDENTITY(1,1) PRIMARY KEY,
    conta       VARCHAR(10)  NOT NULL,
    token_hash  CHAR(64)     NOT NULL,   -- sha256 del token
    expira_em   DATETIME     NOT NULL,
    usado       BIT          NOT NULL DEFAULT 0,
    ip_origem   VARCHAR(45)  NULL,
    criado_em   DATETIME     NOT NULL DEFAULT GETDATE()
);

Guardar solo el token_hash (y no el token puro) es una decisión de seguridad deliberada: incluso con acceso a la base, nadie puede armar el enlace de recuperación.

Etapa 1: solicitar recuperación

El jugador informa el correo. El sistema busca la cuenta asociada, genera un token aleatorio fuerte, guarda el hash y envía el enlace por correo. Un detalle importante: la respuesta al usuario debe ser la misma exista o no la cuenta, para no revelar cuáles correos están registrados.

<?php
// solicitar-recuperacao.php
require 'src/Mailer.php';
require 'src/db.php';

function solicitarRecuperacao(string $email): void
{
    $pdo = getPDO();

    // Buscar la cuenta por el correo (la columna varía por versión)
    $stmt = $pdo->prepare("SELECT memb_id FROM MEMB_INFO WHERE mail_addr = ?");
    $stmt->execute([$email]);
    $conta = $stmt->fetchColumn();

    // Responde siempre igual, exista o no la cuenta
    if ($conta) {
        $token     = bin2hex(random_bytes(32));       // token puro (solo en el correo)
        $tokenHash = hash('sha256', $token);
        $expira    = date('Y-m-d H:i:s', time() + 1800); // 30 min

        $ins = $pdo->prepare(
            "INSERT INTO WEB_PASSWORD_RESET (conta, token_hash, expira_em, ip_origem)
             VALUES (?, ?, ?, ?)"
        );
        $ins->execute([$conta, $tokenHash, $expira, $_SERVER['REMOTE_ADDR'] ?? null]);

        $link = "https://tuserver.com/redefinir?conta=" . urlencode($conta)
              . "&token=" . $token;

        $html = "<p>Recibimos una solicitud para restablecer tu contraseña.</p>
                 <p><a href=\"$link\">Haz clic aquí para crear una nueva contraseña</a>.</p>
                 <p>El enlace expira en 30 minutos. Si no fuiste tú, ignora este correo.</p>";

        enviarEmail($email, 'Recuperación de contraseña - ViciadosMU', $html);
    }
    // Mensaje neutro para el usuario, siempre
    echo 'Si este correo está registrado, recibirás las instrucciones en instantes.';
}

Etapa 2: validar el token y restablecer la contraseña

Cuando el jugador hace clic en el enlace, el sistema valida el token contra el hash, comprueba expiración y uso, y solo entonces permite definir la nueva contraseña. Al final, el token se marca como usado; nunca reutilizable.

<?php
// redefinir.php
require 'src/db.php';

function redefinirSenha(string $conta, string $token, string $novaSenha): array
{
    $pdo = getPDO();

    if (strlen($novaSenha) < 6) {
        return ['ok' => false, 'msg' => 'La contraseña debe tener al menos 6 caracteres'];
    }

    $stmt = $pdo->prepare(
        "SELECT id, expira_em, usado FROM WEB_PASSWORD_RESET
         WHERE conta = ? AND token_hash = ?"
    );
    $stmt->execute([$conta, hash('sha256', $token)]);
    $reg = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$reg) {
        return ['ok' => false, 'msg' => 'Enlace inválido'];
    }
    if ($reg['usado']) {
        return ['ok' => false, 'msg' => 'Este enlace ya fue utilizado'];
    }
    if (strtotime($reg['expira_em']) < time()) {
        return ['ok' => false, 'msg' => 'Este enlace expiró. Solicítalo de nuevo.'];
    }

    // El formato de contraseña VARÍA POR VERSIÓN — confirma el de tu MuServer
    // Ejemplo común: MD5 en mayúsculas
    $senhaCript = strtoupper(md5($novaSenha));

    $pdo->beginTransaction();
    $pdo->prepare("UPDATE MEMB_INFO SET memb_pw = ? WHERE memb_id = ?")
        ->execute([$senhaCript, $conta]);
    $pdo->prepare("UPDATE WEB_PASSWORD_RESET SET usado = 1 WHERE id = ?")
        ->execute([$reg['id']]);
    $pdo->commit();

    return ['ok' => true, 'msg' => '¡Contraseña cambiada con éxito! Ya puedes entrar al juego.'];
}

Usar una transacción garantiza que la contraseña solo se considera cambiada si el token también se invalida en el mismo movimiento, sin ventana para reuso.

Protegiendo contra el abuso

Un formulario de recuperación abierto es una invitación al abuso: alguien puede disparar cientos de correos o intentar adivinar tokens. Algunas defensas simples resuelven la mayor parte:

  • Límite de solicitudes por IP y por cuenta — como máximo, por ejemplo, 3 pedidos cada 15 minutos.
  • Token largo y aleatorio — 32 bytes de random_bytes son imposibles de adivinar por fuerza bruta.
  • CAPTCHA en el formulario de solicitud, para contener bots.
  • Expiración corta — 15 a 30 minutos es suficiente para que el jugador actúe.
  • Invalidación de tokens antiguos — al generar uno nuevo, marca los anteriores de la misma cuenta como usados.
  • Log de intentos, guardando IP y hora, para investigar picos sospechosos.

Errores comunes y soluciones

ErrorCausa probableSolución
El correo cae siempre en spamFalta de SPF/DKIM/DMARCPublica los tres registros DNS y valida antes de producción
SMTP connect() failedPuerto/seguridad equivocados o firewallRevisa el puerto 587/465 y libera la salida en el firewall del servidor
Could not authenticateUsuario o contraseña SMTP incorrectosGenera contraseña de app (Gmail) o API key (transaccional) y vuelve a probar
openssl ausenteExtensión deshabilitada en PHPHabilita extension=openssl en el php.ini y reinicia
El enlace de recuperación no funcionaComparando token puro con hashAplica hash('sha256', $token) en la validación
Token reutilizableFalta marcar usado = 1Invalida el token dentro de la misma transacción del cambio de contraseña
Filtración de cuentas existentesMensajes diferentes por correoResponde siempre el mismo mensaje neutro

Probando el flujo completo

  1. Configura el SMTP y envía un correo de prueba aislado; confirma que llega a la bandeja de entrada, no al spam.
  2. Publica SPF, DKIM y DMARC y vuelve a validar la prueba.
  3. Solicita recuperación con un correo registrado y confirma la recepción del enlace.
  4. Solicita con un correo inexistente y verifica que el mensaje mostrado sea idéntico.
  5. Haz clic en el enlace, define una nueva contraseña e inicia sesión en el juego con ella.
  6. Intenta usar el mismo enlace de nuevo — debe ser rechazado como "ya utilizado".
  7. Espera la expiración y prueba un enlace vencido — debe pedir una nueva solicitud.

Lista de verificación de lanzamiento

  • PHPMailer instalado vía Composer y openssl habilitado
  • Credenciales SMTP en archivo fuera del versionamiento
  • Correo de prueba llegando a la bandeja de entrada (no spam)
  • SPF, DKIM y DMARC publicados y validados
  • Tabla WEB_PASSWORD_RESET creada
  • Token guardado solo como hash sha256
  • Expiración de token configurada (15-30 min)
  • Token invalidado tras el primer uso (transacción)
  • Mensaje neutro que no revela cuentas existentes
  • Rate limiting y CAPTCHA en el formulario de solicitud
  • Formato de contraseña confirmado para tu versión del MuServer
  • Flujo completo probado de punta a punta

Preguntas frecuentes

¿Puedo usar Gmail como SMTP del servidor?

Puedes para pruebas y servidores pequeños, pero Gmail impone límites diarios y exige contraseña de aplicación. Para producción, un proveedor transaccional (Brevo, Mailgun, Amazon SES) entrega mejor y no bloquea tu cuenta.

¿Por qué los correos de mi sitio caen en spam?

Casi siempre por falta de SPF, DKIM y DMARC en el dominio, o por enviar desde una IP con mala reputación. Configurar esos tres registros DNS es el paso que más mejora la entrega.

¿El token de recuperación necesita expirar?

Sí. Un token sin expiración se convierte en una llave permanente para invadir la cuenta. Lo estándar es expirar entre 15 y 60 minutos e invalidarlo en cuanto se usa una vez.

¿Necesito guardar el token en texto plano en la base de datos?

No, y no deberías. Guarda solo el hash del token. Si la base se filtra, nadie puede reconstruir los enlaces de recuperación a partir de los hashes.

¿SMTP con SSL o TLS, cuál elegir?

Usa el puerto 587 con STARTTLS en la mayoría de los proveedores modernos, o el puerto 465 con SSL implícito cuando el proveedor lo exija. Evita el puerto 25 sin cifrado, que suele estar bloqueado y es inseguro.

GA
Editor de guías y builds

Gabriel cubre gameplay, builds de clases, PvP y progresión. Prueba cada estrategia en un servidor antes de publicar.

Sigue leyendo

Artículos relacionados