Jump to content

Problema com envio de emails dinâmicos


Go to solution Solved by security,

Recommended Posts

Posted

Ola a todos,
Tenho um sistema que estou a desenvolver e preciso que envie email sempre que existe um registo de um novo master user ou então quando é Solicitado o reenvio do email de ativação de conta, que acontece quando é definida a password, atraves de um email que contem um link (email convite)! Pois bem, agora vamos entrar no pior, 
no sistema o master user ativo (OWNER ou SUPER_ADMIN) podem criar regras de notificação Selecionando:
 -  Evento (NEW_MASTER_USER, INVITE_MASTER_USER, LOGIN_BLOCKED, PASSWORD_RESET, SECURITY_ALERT, SYSTEM_ERROR)
 - Tipo (Utilizador, Perfil (Role), Email Externo)
 - Destino (Depende do tipo 
      - Tipo: Utilizador
            - Destino: Seleciona Master User Ativo num <select>
      - Tipo: Perfil (Role)
            - Destino: OWNER, SUPER_ADMIN, ADMIN, FINANCEIRO (uma das opçoes)
      - Tipo: Email externo
            - Destino: Email a escolha escrito pelo utilizador

Acontece que o sistema nao envia os emails devidos e da a informação que foi Reenviado! Abaixo vou colocar o código dos ficheiros com o respetivo caminho que acho que são relevantes para ver se alguem me consegue ajudar...

config/config.php

<?php
declare(strict_types=1);

date_default_timezone_set('UTC');

/* ================= ENV ================= */
function env(string $key, $default = null)
{
    static $vars = null;

    if ($vars === null) {
        $vars = [];
        $path = __DIR__ . '/../.env';

        if (file_exists($path)) {
            foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
                if (str_starts_with(trim($line), '#')) continue;
                [$k, $v] = explode('=', $line, 2);
                $vars[$k] = trim($v, "\"'");
            }
        }
    }
    return $vars[$key] ?? $default;
}

/* ================= MAIL ================= */
define('MAIL_ENABLED', env('MAIL_ENABLED', 'false') === 'true');
define('MAIL_HOST', env('MAIL_HOST'));
define('MAIL_PORT', (int)env('MAIL_PORT', 587));
define('MAIL_ENCRYPTION', env('MAIL_ENCRYPTION', 'tls'));
define('MAIL_USERNAME', env('MAIL_USERNAME'));
define('MAIL_PASSWORD', env('MAIL_PASSWORD'));
define('MAIL_FROM_EMAIL', env('MAIL_FROM_EMAIL'));
define('MAIL_FROM_NAME', env('MAIL_FROM_NAME'));

/* ================= SESSION ================= */
if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

/* ================= PDO ================= */
require_once __DIR__ . '/database.php';

/* ================= CORE ================= */
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/security.php';
/* ================= SERVICES ================= */
require_once __DIR__ . '/../services/EmailLogger.php';
require_once __DIR__ . '/../services/EmailService.php';


define('BASE_URL', 'https://fleetlicenser.mabpinto.ddns.net');


/services/EmailService.php

<?php
declare(strict_types=1);

use PHPMailer\PHPMailer\PHPMailer;

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

final class EmailService
{
    public static function send(
        string $eventKey,
        array $variables = [],
        array $context = []
    ): void {
        if (!MAIL_ENABLED) {
            return;
        }

        global $pdo;
        if (!$pdo instanceof PDO) {
            error_log('[EMAIL] PDO indisponível');
            return;
        }

        /* EVENTO */
        $stmt = $pdo->prepare("
            SELECT id_event FROM tb_email_event
            WHERE event_key = ? AND ativo = 1
        ");
        $stmt->execute([$eventKey]);
        if (!$stmt->fetchColumn()) {
            error_log("[EMAIL] Evento inválido: {$eventKey}");
            return;
        }

        /* TEMPLATE */
        $stmt = $pdo->prepare("
            SELECT subject, body_html, body_text
            FROM tb_email_template
            WHERE event_key = ? AND active = 1
            LIMIT 1
        ");
        $stmt->execute([$eventKey]);
        $tpl = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$tpl) {
            error_log("[EMAIL] Template inexistente: {$eventKey}");
            return;
        }

        /* RECIPIENTS */
        $stmt = $pdo->prepare("
            SELECT r.recipient_type, r.recipient_value
            FROM tb_email_recipient r
            JOIN tb_email_event e ON e.id_event = r.id_event
            WHERE e.event_key = ? AND r.enabled = 1 AND e.ativo = 1
        ");
        $stmt->execute([$eventKey]);

        $emails = [];
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
            if ($r['recipient_type'] === 'EMAIL') {
                $emails[] = $r['recipient_value'];
            }
        }

        if (!$emails) {
            error_log("[EMAIL] Sem destinatários: {$eventKey}");
            return;
        }

        /* SMTP */
        try {
            $mail = new PHPMailer(true);
            $mail->isSMTP();
            $mail->Host = MAIL_HOST;
            $mail->SMTPAuth = true;
            $mail->Username = MAIL_USERNAME;
            $mail->Password = MAIL_PASSWORD;
            $mail->Port = MAIL_PORT;
            $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
            $mail->setFrom(MAIL_FROM_EMAIL, MAIL_FROM_NAME);

            foreach ($emails as $e) {
                $mail->addAddress($e);
            }

            $mail->isHTML(true);
            $mail->Subject = self::parse($tpl['subject'], $variables);
            $mail->Body = self::parse($tpl['body_html'], $variables);
            $mail->AltBody = strip_tags($mail->Body);

            $mail->send();

            EmailLogger::log($pdo, $eventKey, $emails, 'SENT', null, $context);
        } catch (Throwable $e) {
            EmailLogger::log($pdo, $eventKey, $emails, 'ERROR', $e->getMessage(), $context);
            error_log('[EMAIL ERROR] ' . $e->getMessage());
        }
    }

    private static function parse(string $text, array $vars): string {
        foreach ($vars as $k => $v) {
            $text = str_replace('{{'.$k.'}}', (string)$v, $text);
        }
        return $text;
    }
}

/services/EmailLogger.php

<?php
declare(strict_types=1);

class EmailLogger
{
    public static function log(
        PDO $pdo,
        string $eventKey,
        array $recipients,
        string $status,
        ?string $error = null,
        array $context = []
    ): void {
        $stmt = $pdo->prepare("
            INSERT INTO tb_email_log
            (event_key, recipients, status, error_message, context)
            VALUES (?, ?, ?, ?, ?)
        ");

        $stmt->execute([
            $eventKey,
            json_encode($recipients, JSON_UNESCAPED_UNICODE),
            $status,
            $error,
            json_encode($context, JSON_UNESCAPED_UNICODE)
        ]);
    }
}

/modules/master/master.php

     <!-- REENVIAR CONVITE -->
    <?php if ($u['estado'] === 'pendente'): ?>
        <form method="post" action="master_user_resend_invite.php" class="d-inline"
            onsubmit="return confirm('Reenviar convite por email?')">

            <input type="hidden" name="csrf_token" value="<?= csrfToken() ?>">
            <input type="hidden" name="id_user" value="<?= $u['id_user'] ?>">
            <button class="btn btn-sm btn-outline-info" title="Reenviar convite">
                <i class="bi bi-envelope"></i>
            </button>
        </form>
    <?php endif; ?>

    <?php elseif ($u['estado'] === 'inativo'): ?>

/modules/master/master_user_resend_invite.php

<?php
declare(strict_types=1);

require '../../config/config.php';
requireMasterLogin();
requireRole(['OWNER','SUPER_ADMIN']);

validateCsrf($_POST['csrf_token'] ?? '');

$userId = (int)($_POST['id_user'] ?? 0);
if ($userId <= 0) {
    $_SESSION['error'] = 'Utilizador inválido.';
    header('Location: master.php');
    exit;
}

/* USER */
$stmt = $pdo->prepare("
    SELECT id_user, nome, email, estado
    FROM tb_master_user
    WHERE id_user = ?
");
$stmt->execute([$userId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$user || $user['estado'] !== 'pendente') {
    $_SESSION['error'] = 'Utilizador inválido ou não pendente.';
    header('Location: master.php');
    exit;
}

/* TOKEN */
$token = bin2hex(random_bytes(32));
$expires = date('Y-m-d H:i:s', strtotime('+48 hours'));

$pdo->prepare("
    INSERT INTO tb_user_invite_token (id_user, token, expires_at)
    VALUES (?, ?, ?)
")->execute([$userId, $token, $expires]);

$link = BASE_URL . "/auth/define_password.php?token={$token}";

/* EMAIL */
EmailService::send(
    'INVITE_MASTER_USER',
    [
        'user_name'   => $user['nome'],
        'invite_link' => $link
    ],
    [
        'id_user' => $userId,
        'origin'  => 'resend_invite'
    ]
);

$_SESSION['success'] = 'Convite reenviado com sucesso.';
header('Location: master.php');
exit;

Inicialmente fiz a configuração de email, sem passar pelas regras funcionou. Agora que inseri as regras, penso que nao chama nem a funçãoEmailService::send

Ja não sei mais como fazer.

Isto está no meu servidor.

Agradeço muito desde ja a todos que me possam ajudar!

Obrigado e um bom ano!!!

"Innovation distinguishes between a leader and a follower." Steve jobs.

Posted
Em 01/01/2026 às 04:54, JakeBass disse:

E o quê que te aparece nos logs?

Absolutamente nada! Teoricamente esta correto mas na prática parece que  nao chama a função. Esta a falhar silenciosamente !!!

"Innovation distinguishes between a leader and a follower." Steve jobs.

Posted

Entretanto decidi fazer um ficheiro de teste para me apresentar o passo a passo, para ver onde passava a ordem, não é um formato complexo e o resultado surpreendeu, segundo o teste passou por todo lado que tinha de passar e chegou ao final com sucesso!!!

 

== TESTE EmailService ==

✔ PDO OK
✔ Evento encontrado (id_event=4)
✔ Template encontrado
✔ Destinatários configurados:
Array
(
    [0] => Array
        (
            [recipient_type] => EMAIL
            [recipient_value] => mabpinto@outlook.pt
        )

)

== A chamar EmailService::send() ==
✔ EmailService::send() executado SEM exception

== FIM DO TESTE ==

 

"Innovation distinguishes between a leader and a follower." Steve jobs.

  • Solution
Posted

Sim ja, nao estava a aparecer nada dlnos logs. Mudei a forma como enviava os emails, em vez de enviar direto atraves do meu email, experimentei usar a brevo.com mas ainda assim apenas consegui enviar o email de teste, quando inseria no sistema falhava o email. Aí já aparecia nos logs authentication falied.

 

A solucao foi adaptar o codigo para enviar os emails atraves da API da brevo. Agora consigo enviar sem problemas.

 

Para conseguir fazer isso tive de criar uma espécie de email-tester a parte. No meu projecto desvio o envio de emails para esse email tester e assim ja consigo enviar, além que tambem consigo usar com outros aistemas

"Innovation distinguishes between a leader and a follower." Steve jobs.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.