Development and databases

Generate CSV reports of email and cron from cPanel

Reviewed on 8 min read cpanel api reportes

You can use UAPI to inventory the mailboxes, forwarders and autoresponders in your account. The following examples run with PHP 8 over SSH; they are not pages to publish on the web. Read the introduction to UAPI first.

Prepare a private folder

Keep the scripts and their output outside public_html and the document roots of your other domains, for example in /home/usuario_cpanel/reportes. Restrict access to your user: the reports contain addresses and may include commands with private information.

Before automating anything, run each query call in your account and confirm which fields the installed version returns. The functions depend on the services and permissions available. An empty report does not prove there are no accounts if the call has failed.

Common functions

Save this file as funciones.php in the private folder. Check that exec is available in the terminal's PHP. The function only allows the fixed queries used in these examples and checks both the process and the UAPI status.

<?php

function ejecutar(string $comando): string
{
    $lineas = [];
    $codigo = 0;
    exec($comando, $lineas, $codigo);
    if ($codigo !== 0) {
        throw new RuntimeException('La consulta no terminó correctamente.');
    }
    return implode("\n", $lineas);
}

function uapi(string $funcion, array $parametros = []): array
{
    $permitidas = [
        'list_pops_with_disk', 'list_forwarders',
        'list_auto_responders', 'get_auto_responder',
    ];
    if (!in_array($funcion, $permitidas, true)) {
        throw new InvalidArgumentException('Función no admitida.');
    }
    $comando = '/usr/local/cpanel/bin/uapi --output=json Email '
        . escapeshellarg($funcion);
    foreach ($parametros as $clave => $valor) {
        if (!in_array($clave, ['domain', 'email'], true)) {
            throw new InvalidArgumentException('Parámetro no admitido.');
        }
        $comando .= ' ' . escapeshellarg($clave . '=' . $valor);
    }
    $respuesta = json_decode(ejecutar($comando), true, 512, JSON_THROW_ON_ERROR);
    $resultado = $respuesta['result'] ?? [];
    if ((int) ($resultado['status'] ?? 0) !== 1) {
        throw new RuntimeException('UAPI rechazó la consulta; revisa permisos y parámetros.');
    }
    if (!is_array($resultado['data'] ?? null)) {
        throw new RuntimeException('UAPI devolvió un formato de datos inesperado.');
    }
    return $resultado['data'];
}

function escribirCsv(array $filas): void
{
    foreach ($filas as $fila) {
        $valores = array_map(static function ($valor): string {
            $texto = (string) ($valor ?? '');
            // Evita que textos del informe se interpreten como fórmulas.
            if (preg_match('/^[\s]*[=+@-]/u', $texto)) {
                $texto = "'" . $texto;
            }
            return $texto;
        }, $fila);
        if (fputcsv(STDOUT, $valores, ';', '"', '') === false) {
            throw new RuntimeException('No se pudo escribir el CSV.');
        }
    }
}

fputcsv handles delimiters, quotes and line breaks correctly. Where appropriate, also import the columns as text in your spreadsheet; avoid opening reports from unknown sources with formulas enabled.

Mailboxes and disk usage

Save as buzones.php:

<?php
require __DIR__ . '/funciones.php';

$filas = [['Email', 'Espacio usado', 'Cuota']];
foreach (uapi('list_pops_with_disk') as $cuenta) {
    $filas[] = [
        $cuenta['email'] ?? '',
        $cuenta['humandiskused'] ?? '',
        $cuenta['humandiskquota'] ?? '',
    ];
}
escribirCsv($filas);

The disk fields are formatted for people to read and may include units or an unlimited quota value. Do not add them up as if they were byte counts. Check the available fields in list_pops_with_disk if you need another format.

Forwarders for a domain

Save as reenviadores.php and change the domain:

<?php
require __DIR__ . '/funciones.php';

$filas = [['Origen', 'Destino']];
foreach (uapi('list_forwarders', ['domain' => 'example.com']) as $regla) {
    $filas[] = [$regla['dest'] ?? '', $regla['forward'] ?? ''];
}
escribirCsv($filas);

The example explicitly filters one domain. Repeat the query for the domains you want to include and make sure you do not duplicate results. A destination address may be external; the list does not prove it is delivering mail. Reference: list_forwarders.

Autoresponders

The per-domain list identifies the configured autoresponders. Save it as respuestas.php:

<?php
require __DIR__ . '/funciones.php';

$filas = [['Cuenta', 'Asunto']];
foreach (uapi('list_auto_responders', ['domain' => 'example.com']) as $respuesta) {
    $filas[] = [$respuesta['email'] ?? '', $respuesta['subject'] ?? ''];
}
escribirCsv($filas);

See list_auto_responders for your server's format. If you need dates, also look at get_auto_responder: check the identifier it accepts and validate a known autoresponder before repeating that query for every account. Do not automatically strip the domain from an email address; you could query another identity or not get the result you expect.

The start and end fields may be null. If you export Unix timestamps, convert them with an explicit time zone and show that zone in the header. A configured autoresponder does not mean it is active at the time of the report.

Cron jobs

You can list the user's jobs with crontab -l. First keep its original output as a private reference. To generate a CSV of the usual schedule lines, save cron.php:

<?php
require __DIR__ . '/funciones.php';

$filas = [['Programación', 'Comando']];
foreach (explode("\n", ejecutar('crontab -l')) as $linea) {
    $linea = trim($linea);
    if ($linea === '' || str_starts_with($linea, '#')
        || preg_match('/^[A-Za-z_][A-Za-z0-9_]*\s*=/', $linea)) {
        continue;
    }
    if (preg_match('/^(@\S+)\s+(.+)$/', $linea, $partes)) {
        $filas[] = [$partes[1], $partes[2]];
    } else {
        $partes = preg_split('/\s+/', $linea, 6);
        if (count($partes) !== 6) {
            throw new RuntimeException('Hay una línea cron no reconocida.');
        }
        $filas[] = [implode(' ', array_slice($partes, 0, 5)), $partes[5]];
    }
}
escribirCsv($filas);

If the user has no crontab, the command may exit with an error: check the message in the terminal before interpreting the result. This CSV leaves out environment variables and comments; it is for inventory, not for restoring a complete schedule. It does not cover other users' or system jobs.

Generate and check each report

For example:

umask 077
php /home/usuario_cpanel/reportes/buzones.php > /home/usuario_cpanel/reportes/buzones.csv

Use a new output name and check that the command finishes without errors. Do not use the file if it has failed: the redirection can leave an empty or partial CSV. Check the header, a known row and the count before downloading it. Delete temporary copies when you no longer need them.

You may also find useful

Something wrong or out of date? Tell us and we will review it.