email = $email;
$this->accessToken = $accessToken;
}
public function getOauth64(): string {
$auth = "user={$this->email}\x01auth=Bearer {$this->accessToken}\x01\x01";
return base64_encode($auth);
}
}
/** =======================================================================
* Load settings
* ======================================================================= */
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1");
$row = mysqli_fetch_array($sql_settings);
$config_enable_cron = intval($row['config_enable_cron']);
// SMTP baseline
$config_smtp_host = $row['config_smtp_host'];
$config_smtp_username = $row['config_smtp_username'];
$config_smtp_password = $row['config_smtp_password'];
$config_smtp_port = intval($row['config_smtp_port']);
$config_smtp_encryption = $row['config_smtp_encryption'];
// SMTP provider + shared OAuth fields
$config_smtp_provider = $row['config_smtp_provider']; // 'standard_smtp' | 'google_oauth' | 'microsoft_oauth'
$config_mail_oauth_client_id = $row['config_mail_oauth_client_id'] ?? '';
$config_mail_oauth_client_secret = $row['config_mail_oauth_client_secret'] ?? '';
$config_mail_oauth_tenant_id = $row['config_mail_oauth_tenant_id'] ?? '';
$config_mail_oauth_refresh_token = $row['config_mail_oauth_refresh_token'] ?? '';
$config_mail_oauth_access_token = $row['config_mail_oauth_access_token'] ?? '';
$config_mail_oauth_access_token_expires_at = $row['config_mail_oauth_access_token_expires_at'] ?? '';
if ($config_enable_cron == 0) {
logApp("Cron-Mail-Queue", "error", "Cron Mail Queue unable to run - cron not enabled in admin settings.");
exit("Cron: is not enabled -- Quitting..");
}
if (empty($config_smtp_provider)) {
logApp("Cron-Mail-Queue", "info", "SMTP sending skipped: provider not configured.");
exit(0);
}
/** =======================================================================
* Lock file
* ======================================================================= */
$temp_dir = sys_get_temp_dir();
$lock_file_path = "{$temp_dir}/itflow_mail_queue_{$installation_id}.lock";
if (file_exists($lock_file_path)) {
$file_age = time() - filemtime($lock_file_path);
if ($file_age > 600) {
unlink($lock_file_path);
logApp("Cron-Mail-Queue", "warning", "Cron Mail Queue detected a lock file was present but was over 10 minutes old so it removed it.");
} else {
logApp("Cron-Mail-Queue", "info", "Cron Mail Queue attempted to execute but was already executing so instead it terminated.");
exit("Script is already running. Exiting.");
}
}
file_put_contents($lock_file_path, "Locked");
/** =======================================================================
* Mail sender function (defined inside this cron)
* - Handles standard SMTP and XOAUTH2 for Google/Microsoft
* - Reuses shared OAuth settings
* ======================================================================= */
function sendQueueEmail(
string $provider,
string $host,
int $port,
string $encryption,
string $username,
string $password,
string $from_email,
string $from_name,
string $to_email,
string $to_name,
string $subject,
string $html_body,
string $ics_str,
string $oauth_client_id,
string $oauth_client_secret,
string $oauth_tenant_id,
string $oauth_refresh_token,
string $oauth_access_token,
string $oauth_access_token_expires_at
) {
// Sensible defaults for OAuth providers if fields were left blank
if ($provider === 'google_oauth') {
if (!$host) $host = 'smtp.gmail.com';
if (!$port) $port = 587;
if (!$encryption) $encryption = 'tls';
if (!$username) $username = $from_email;
} elseif ($provider === 'microsoft_oauth') {
if (!$host) $host = 'smtp.office365.com';
if (!$port) $port = 587;
if (!$encryption) $encryption = 'tls';
if (!$username) $username = $from_email;
}
$mail = new PHPMailer(true);
$mail->CharSet = "UTF-8";
$mail->SMTPDebug = 0;
$mail->isSMTP();
$mail->Host = $host;
$mail->Port = $port;
$enc = strtolower($encryption);
if ($enc === '' || $enc === 'none') {
$mail->SMTPAutoTLS = false;
$mail->SMTPSecure = false;
$mail->SMTPOptions = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]];
} else {
$mail->SMTPSecure = $enc; // 'tls' | 'ssl'
}
if ($provider === 'google_oauth' || $provider === 'microsoft_oauth') {
// XOAUTH2
$mail->SMTPAuth = true;
$mail->AuthType = 'XOAUTH2';
$mail->Username = $username;
// Pick/refresh access token
$accessToken = trim($oauth_access_token);
$needsRefresh = empty($accessToken);
if (!$needsRefresh && !empty($oauth_access_token_expires_at)) {
$expTs = strtotime($oauth_access_token_expires_at);
if ($expTs && $expTs <= time() + 60) $needsRefresh = true;
}
if ($needsRefresh) {
if ($provider === 'google_oauth' && function_exists('getGoogleAccessToken')) {
$accessToken = getGoogleAccessToken($username);
} elseif ($provider === 'microsoft_oauth' && function_exists('getMicrosoftAccessToken')) {
$accessToken = getMicrosoftAccessToken($username);
}
}
if (empty($accessToken)) {
throw new Exception("Missing OAuth access token for XOAUTH2 SMTP.");
}
$mail->setOAuth(new StaticTokenProvider($username, $accessToken));
} else {
// Standard SMTP (with or without auth)
$mail->SMTPAuth = !empty($username);
$mail->Username = $username ?: '';
$mail->Password = $password ?: '';
}
// Recipients & content
$mail->setFrom($from_email, $from_name);
$mail->addAddress($to_email, $to_name);
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = "
{$html_body}
";
if (!empty($ics_str)) {
$mail->addStringAttachment($ics_str, 'Scheduled_ticket.ics', 'base64', 'text/calendar');
}
$mail->send();
return true;
}
/** =======================================================================
* SEND: status = 0 (Queued)
* ======================================================================= */
$sql_queue = mysqli_query($mysqli, "SELECT * FROM email_queue WHERE email_status = 0 AND email_queued_at <= NOW()");
if (mysqli_num_rows($sql_queue) > 0) {
while ($rowq = mysqli_fetch_array($sql_queue)) {
$email_id = (int)$rowq['email_id'];
$email_from = $rowq['email_from'];
$email_from_name = $rowq['email_from_name'];
$email_recipient = $rowq['email_recipient'];
$email_recipient_name = $rowq['email_recipient_name'];
$email_subject = $rowq['email_subject'];
$email_content = $rowq['email_content'];
$email_ics_str = $rowq['email_cal_str'];
// Check sender
if (!filter_var($email_from, FILTER_VALIDATE_EMAIL)) {
$email_from_logging = sanitizeInput($rowq['email_from']);
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
logApp("Cron-Mail-Queue", "Error", "Failed to send email #$email_id due to invalid sender address: $email_from_logging - check configuration in settings.");
appNotify("Mail", "Failed to send email #$email_id due to invalid sender address");
continue;
}
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id");
// Check recipient
$domain = sanitizeInput(substr($email_recipient, strpos($email_recipient, '@') + 1));
if (!filter_var($email_recipient, FILTER_VALIDATE_EMAIL) || !checkdnsrr($domain, 'MX')) {
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = 99 WHERE email_id = $email_id");
$email_to_logging = sanitizeInput($email_recipient);
$email_subject_logging = sanitizeInput($rowq['email_subject']);
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_to_logging due to invalid recipient address. Email subject was: $email_subject_logging");
appNotify("Mail", "Failed to send email #$email_id to $email_to_logging due to invalid recipient address: Email subject was: $email_subject_logging");
continue;
}
try {
sendQueueEmail(
($config_smtp_provider ?: 'standard_smtp'),
$config_smtp_host,
(int)$config_smtp_port,
(string)$config_smtp_encryption,
(string)$config_smtp_username,
(string)$config_smtp_password,
(string)$email_from,
(string)$email_from_name,
(string)$email_recipient,
(string)$email_recipient_name,
(string)$email_subject,
(string)$email_content,
(string)$email_ics_str,
(string)$config_mail_oauth_client_id,
(string)$config_mail_oauth_client_secret,
(string)$config_mail_oauth_tenant_id,
(string)$config_mail_oauth_refresh_token,
(string)$config_mail_oauth_access_token,
(string)$config_mail_oauth_access_token_expires_at
);
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = 1 WHERE email_id = $email_id");
} catch (Exception $e) {
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = 1 WHERE email_id = $email_id");
$email_recipient_logging = sanitizeInput($rowq['email_recipient']);
$email_subject_logging = sanitizeInput($rowq['email_subject']);
$err = substr("Mailer Error: " . $e->getMessage(), 0, 100) . "...";
appNotify("Cron-Mail-Queue", "Failed to send email #$email_id to $email_recipient_logging");
logApp("Cron-Mail-Queue", "Error", "Failed to send email: $email_id to $email_recipient_logging regarding $email_subject_logging. $err");
}
}
}
/** =======================================================================
* RETRIES: status = 2 (Failed), attempts < 4, wait 30 min
* NOTE: Backoff is `email_failed_at <= NOW() - INTERVAL 30 MINUTE`
* =======================================================================*/
$sql_failed_queue = mysqli_query(
$mysqli,
"SELECT * FROM email_queue
WHERE email_status = 2
AND email_attempts < 4
AND email_failed_at <= NOW() - INTERVAL 30 MINUTE"
);
if (mysqli_num_rows($sql_failed_queue) > 0) {
while ($rowf = mysqli_fetch_array($sql_failed_queue)) {
$email_id = (int)$rowf['email_id'];
$email_from = $rowf['email_from'];
$email_from_name = $rowf['email_from_name'];
$email_recipient = $rowf['email_recipient'];
$email_recipient_name = $rowf['email_recipient_name'];
$email_subject = $rowf['email_subject'];
$email_content = $rowf['email_content'];
$email_ics_str = $rowf['email_cal_str'];
$email_attempts = (int)$rowf['email_attempts'] + 1;
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 1 WHERE email_id = $email_id");
if (!filter_var($email_recipient, FILTER_VALIDATE_EMAIL)) {
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_attempts = $email_attempts WHERE email_id = $email_id");
continue;
}
try {
sendQueueEmail(
($config_smtp_provider ?: 'standard_smtp'),
$config_smtp_host,
(int)$config_smtp_port,
(string)$config_smtp_encryption,
(string)$config_smtp_username,
(string)$config_smtp_password,
(string)$email_from,
(string)$email_from_name,
(string)$email_recipient,
(string)$email_recipient_name,
(string)$email_subject,
(string)$email_content,
(string)$email_ics_str,
(string)$config_mail_oauth_client_id,
(string)$config_mail_oauth_client_secret,
(string)$config_mail_oauth_tenant_id,
(string)$config_mail_oauth_refresh_token,
(string)$config_mail_oauth_access_token,
(string)$config_mail_oauth_access_token_expires_at
);
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 3, email_sent_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id");
} catch (Exception $e) {
mysqli_query($mysqli, "UPDATE email_queue SET email_status = 2, email_failed_at = NOW(), email_attempts = $email_attempts WHERE email_id = $email_id");
$email_recipient_logging = sanitizeInput($rowf['email_recipient']);
$email_subject_logging = sanitizeInput($rowf['email_subject']);
$err = substr("Mailer Error: " . $e->getMessage(), 0, 100) . "...";
logApp("Cron-Mail-Queue", "Error", "Failed to re-send email #$email_id to $email_recipient_logging regarding $email_subject_logging. $err");
}
}
}
/** =======================================================================
* Unlock
* ======================================================================= */
unlink($lock_file_path);