Cómo enviar correos automáticos con Bash y Sendmail en Debian 13: scripts, alertas, HTML y archivos adjuntos

Serie: Sendmail en Debian — Parte 5 de 6

En los tutoriales anteriores aprendimos a instalar Sendmail, conectarlo con servidores SMTP de Gmail y hosting, enviar mensajes desde la terminal y diagnosticar errores.

Ahora utilizaremos esos conocimientos para crear herramientas que trabajen automáticamente.

Imaginemos que administramos un servidor Linux y queremos recibir un correo cuando el disco esté demasiado lleno, cuando falle un servicio o cuando termine una copia de seguridad.

Podríamos comprobar manualmente el sistema todos los días, pero Bash nos permite automatizar estas tareas.

En esta quinta parte aprenderemos a desarrollar scripts .sh que utilicen Sendmail para enviar notificaciones, mensajes HTML y archivos adjuntos.

Además, explicaremos cómo programar la ejecución mediante cron y cómo evitar errores comunes relacionados con permisos, variables, codificación y envío de mensajes.


1. ¿Qué aprenderemos?

Durante el tutorial realizaremos seis prácticas:

  1. Crear un script Bash para enviar correos.
  2. Utilizar variables, argumentos y condiciones.
  3. Enviar alertas cuando el disco supere un porcentaje de uso.
  4. Comprobar servicios y enviar notificaciones.
  5. Crear correos HTML y adjuntar archivos mediante MIME.
  6. Programar scripts automáticos con cron.

Importante: este tutorial no requiere configurar nuevamente Gmail ni HostGator. Utilizaremos el servidor SMTP que ya configuramos en las partes anteriores.


2. Requisitos

Necesitaremos:

  • Debian GNU/Linux 13.
  • Sendmail instalado y funcionando.
  • Una cuenta SMTP configurada.
  • Bash.
  • Un editor como Nano.
  • Permisos de ejecución para nuestros scripts.
  • Acceso a la terminal.

También podremos aplicar gran parte del procedimiento en Debian 12, Ubuntu y otras distribuciones que utilicen Bash y Sendmail, aunque las rutas y herramientas de administración pueden variar.

2.1. Comprobar Sendmail

Ejecutamos:

sudo systemctl status sendmail --no-pager

sudo permite ejecutar el comando con privilegios administrativos.

systemctl administra los servicios de systemd.

status consulta el estado.

sendmail identifica el servicio.

--no-pager muestra la información directamente en la terminal.

Debemos comprobar que el servicio esté funcionando.

2.2. Comprobar la ruta de Sendmail

Ejecutamos:

command -v sendmail

command es una instrucción integrada de Bash que permite consultar o ejecutar comandos.

-v muestra cómo se resolvería el nombre del comando.

Podemos obtener:

/usr/sbin/sendmail

Utilizaremos esta ruta en nuestros scripts para evitar depender de la variable PATH.

2.3. Enviar una prueba

Antes de automatizar, comprobaremos que podemos enviar un correo:

echo "Prueba previa a la automatización." | mail -s "Prueba Sendmail" [email protected]

Reemplazaremos la dirección por un destinatario real.

Si el correo no funciona manualmente, debemos corregir la configuración antes de continuar.


3. Crear nuestro directorio de scripts

Primero organizaremos los archivos.

Ejecutamos:

mkdir -p ~/scripts-sendmail

Explicación

mkdir crea directorios.

-p permite crear directorios intermedios cuando sea necesario y evita fallar si el directorio ya existe.

~ representa nuestro directorio personal.

scripts-sendmail será la carpeta donde guardaremos los ejemplos.

Entramos:

cd ~/scripts-sendmail

cd cambia el directorio de trabajo.

Comprobamos nuestra ubicación:

pwd

pwd muestra la ruta absoluta del directorio actual.

Por ejemplo:

/home/systemx/scripts-sendmail

4. Nuestro primer script Bash para enviar correos

Crearemos un archivo:

nano enviar-correo.sh

Escribimos:

#!/usr/bin/env bash

DESTINATARIO="[email protected]"
REMITENTE="[email protected]"
ASUNTO="Mi primer correo automático"

MENSAJE="Hola.

Este correo fue generado automáticamente
desde un script Bash en Debian 13.

Saludos desde Linux."

/usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: $ASUNTO

$MENSAJE
EOF

Debemos reemplazar el destinatario y el remitente.

Si utilizamos Gmail, normalmente emplearemos la cuenta autenticada como remitente.

Si utilizamos el SMTP del hosting, usaremos una dirección autorizada por nuestro proveedor.

4.1. Explicación del script

Primera línea

#!/usr/bin/env bash

Se conoce como shebang.

#! indica al sistema qué intérprete debe utilizar para ejecutar el archivo.

/usr/bin/env permite localizar un programa a través del entorno.

bash es el intérprete que ejecutará nuestro script.

Variables

DESTINATARIO="[email protected]"

Creamos una variable llamada DESTINATARIO.

En Bash no debemos colocar espacios alrededor del signo = cuando asignamos una variable.

Para consultar su valor utilizamos $:

echo "$DESTINATARIO"

Comillas dobles

En:

-f "$REMITENTE"

las comillas dobles permiten expandir la variable y conservar su contenido como un único argumento.

Esto evita problemas cuando una variable contiene espacios o caracteres especiales.

Bloque EOF

<<EOF

Inicia un here-document.

El contenido se proporciona como entrada estándar a Sendmail.

Como EOF no está entre comillas, Bash sustituirá las variables dentro del bloque.

Por ejemplo:

To: $DESTINATARIO

se convertirá en la dirección que definimos anteriormente.

La línea final:

EOF

debe aparecer sola, sin espacios adicionales.

Sendmail

/usr/sbin/sendmail -t -f "$REMITENTE"

-t permite obtener los destinatarios de las cabeceras.

-f establece el remitente del sobre SMTP.

El servidor SMTP puede restringir las direcciones de remitente permitidas.

Nota: este primer ejemplo está pensado para valores definidos por nosotros. Más adelante validaremos los datos recibidos como argumentos para evitar introducir caracteres no permitidos en las cabeceras.


5. Dar permisos y ejecutar nuestro script

Guardamos el archivo en Nano utilizando Ctrl + O, Enter y Ctrl + X.

Después ejecutamos:

chmod 700 enviar-correo.sh

Explicación

chmod modifica permisos.

700 representa:

  • 7: lectura, escritura y ejecución para el propietario.
  • 0: ningún permiso para el grupo.
  • 0: ningún permiso para otros usuarios.

Ahora ejecutamos:

./enviar-correo.sh

./ indica que queremos ejecutar un archivo del directorio actual.

También podríamos utilizar:

bash enviar-correo.sh

En este segundo caso, Bash interpreta el archivo directamente y no necesita que tenga el permiso de ejecución.

5.1. ¿Cómo comprobar si funcionó?

Consultamos:

sudo journalctl -u sendmail -n 30 --no-pager

Buscamos el mensaje y su estado.

Si encontramos:

dsn=2.0.0, stat=Sent

significa que Sendmail registró una entrega satisfactoria al siguiente servidor.

No garantiza por sí solo que el mensaje haya llegado a la bandeja de entrada final.


6. Crear un script reutilizable con argumentos

Nuestro primer script tiene un destinatario fijo.

Ahora aprenderemos a proporcionarlo al ejecutar el programa.

Creamos:

nano enviar-argumento.sh

Agregamos:

#!/usr/bin/env bash

set -euo pipefail

REMITENTE="[email protected]"
DESTINATARIO="${1:-}"
ASUNTO="Notificación desde Debian"

if [[ -z "$DESTINATARIO" ]]; then
    echo "Uso: $0 [email protected]"
    exit 1
fi

if [[ ! "$DESTINATARIO" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
    echo "Error: dirección de correo no válida para este ejemplo."
    exit 1
fi

/usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: $ASUNTO

Este correo fue enviado mediante un argumento de Bash.
EOF

echo "Mensaje entregado al sistema local de correo."

Esta validación acepta direcciones comunes, pero no todas las direcciones técnicamente válidas según los estándares de correo electrónico. Su propósito es evitar entradas inesperadas en este ejemplo.

6.1. ¿Qué significa set -euo pipefail?

set -euo pipefail

Activa tres comportamientos útiles.

-e solicita que Bash termine ante determinados comandos que devuelvan errores. Existen excepciones, por ejemplo en algunas condiciones y listas de comandos.

-u trata como error el uso de variables no definidas, salvo determinadas expansiones que proporcionan valores predeterminados.

-o pipefail hace que el estado de una tubería refleje un fallo de cualquiera de sus comandos, en lugar de considerar únicamente el último.

Estas opciones ayudan a detectar errores, aunque no sustituyen la validación de los datos.

6.2. ¿Qué significa $1?

DESTINATARIO="${1:-}"

$1 representa el primer argumento del script.

${1:-} utiliza una cadena vacía si el argumento no está definido o está vacío.

Esto evita un error por variable no definida cuando utilizamos set -u.

6.3. ¿Qué significa if?

if [[ -z "$DESTINATARIO" ]]; then

if inicia una condición.

[[ ... ]] permite evaluar expresiones condicionales en Bash.

-z comprueba si una cadena está vacía.

then introduce las instrucciones que se ejecutarán cuando la condición sea verdadera.

fi finaliza el bloque condicional.

6.4. ¿Qué significa exit 1?

exit 1

Termina el script devolviendo un código de salida diferente de cero.

Por convención, 0 indica éxito y los valores diferentes de cero indican algún tipo de error.

6.5. Ejecutarlo

Primero:

chmod 700 enviar-argumento.sh

Después:

./enviar-argumento.sh [email protected]

Si ejecutamos:

./enviar-argumento.sh

obtendremos un mensaje indicando cómo utilizarlo.

Importante: que el script termine correctamente solo significa que Sendmail aceptó localmente el mensaje. Todavía debemos comprobar su entrega en los registros.


7. Crear una alerta automática por uso de disco

Ahora realizaremos una práctica útil para la administración de servidores.

Nuestro objetivo será enviar un correo cuando el sistema de archivos raíz alcance un porcentaje de uso determinado.

Por ejemplo, cuando supere el 80 %.

Creamos:

nano alerta-disco.sh

Escribimos:

#!/usr/bin/env bash

set -euo pipefail

DESTINATARIO="[email protected]"
REMITENTE="[email protected]"
LIMITE=80

USO=$(df -P / | awk 'NR==2 {gsub(/%/, "", $5); print $5}')

if [[ "$USO" -ge "$LIMITE" ]]; then

    /usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: Alerta de disco en Debian

ATENCIÓN:

El sistema de archivos raíz ha alcanzado
un uso del ${USO}%.

Límite configurado: ${LIMITE}%.

Equipo: $(hostname)

Revisa el almacenamiento disponible.
EOF

    echo "Alerta enviada al sistema local de correo."

else

    echo "Uso actual: ${USO}%."
    echo "El disco se encuentra por debajo del límite."

fi

7.1. Explicación de df

df -P /

df muestra información sobre el espacio de los sistemas de archivos.

-P utiliza un formato de salida POSIX, más predecible para procesar mediante scripts.

/ indica que queremos consultar el sistema de archivos donde se encuentra la raíz.

7.2. Explicación de awk

awk 'NR==2 {gsub(/%/, "", $5); print $5}'

awk procesa texto organizado en registros y campos.

NR==2 selecciona la segunda línea de la salida, donde normalmente aparecen los datos después de la cabecera.

gsub(/%/, "", $5) elimina el símbolo % del quinto campo.

print $5 muestra ese campo.

Así obtenemos un número como:

67

en lugar de:

67%

7.3. Sustitución de comandos

USO=$(...)

La construcción $() ejecuta un comando y utiliza su salida como valor.

Por eso la variable USO contendrá el porcentaje calculado.

7.4. Comparación numérica

[[ "$USO" -ge "$LIMITE" ]]

-ge significa greater than or equal, es decir, mayor o igual que.

Si el uso es igual o superior al límite, se enviará la alerta.

Si no, el script mostrará el porcentaje sin enviar un correo.

7.5. Probar la alerta

Damos permisos:

chmod 700 alerta-disco.sh

Ejecutamos:

./alerta-disco.sh

Para comprobar el envío sin esperar a que el disco alcance el 80 %, podemos modificar temporalmente:

LIMITE=1

Ejecutamos nuevamente y después restauramos el límite original.

Advertencia: no debemos llenar intencionalmente el disco para probar el script.


8. Evitar correos repetidos por la misma alerta

Existe un problema importante con el ejemplo anterior.

Si programamos la comprobación cada cinco minutos y el disco permanece por encima del 80 %, recibiremos un correo cada cinco minutos.

Esto puede generar demasiadas notificaciones.

Una solución sencilla consiste en guardar el estado de la alerta y enviar un mensaje únicamente cuando el sistema pase de un estado normal a uno de advertencia.

Crearemos una segunda versión.

nano alerta-disco-estado.sh

Agregamos:

#!/usr/bin/env bash

set -euo pipefail

DESTINATARIO="[email protected]"
REMITENTE="[email protected]"

LIMITE=80
ESTADO="$HOME/.alerta-disco-activa"

USO=$(df -P / | awk 'NR==2 {gsub(/%/, "", $5); print $5}')

if [[ "$USO" -ge "$LIMITE" ]]; then

    if [[ ! -f "$ESTADO" ]]; then

        /usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: Alerta de almacenamiento

El disco ha alcanzado un uso del ${USO}%.

Equipo: $(hostname)
EOF

        touch "$ESTADO"

        echo "Nueva alerta generada."

    else

        echo "La alerta ya se encuentra activa."

    fi

else

    if [[ -f "$ESTADO" ]]; then
        rm -- "$ESTADO"
        echo "El uso volvió a un nivel normal."
    fi

fi

¿Qué hace -f?

[[ -f "$ESTADO" ]]

Comprueba si existe un archivo regular en la ruta indicada.

¿Qué hace !?

[[ ! -f "$ESTADO" ]]

El signo ! invierte la condición.

Significa: si el archivo no existe.

¿Qué hace touch?

touch "$ESTADO"

Crea el archivo si no existe o actualiza su marca de tiempo.

Lo utilizamos para recordar que ya enviamos una alerta.

¿Qué hace rm?

rm -- "$ESTADO"

Elimina el archivo de estado cuando el uso del disco vuelve a estar por debajo del límite.

-- indica el final de las opciones del comando, evitando interpretar un nombre de archivo como parámetro.

Este ejemplo funciona para ejecuciones secuenciales. Si lo ejecutamos simultáneamente desde varios procesos, necesitaríamos añadir un mecanismo de bloqueo.

Además, touch registra que Sendmail aceptó localmente el mensaje, no que el destinatario lo recibió.


9. Enviar alertas cuando falle un servicio

Otra utilidad de Bash es comprobar si un servicio está activo.

Por ejemplo, podemos vigilar Apache.

Creamos:

nano alerta-servicio.sh

Agregamos:

#!/usr/bin/env bash

set -euo pipefail

SERVICIO="apache2"
DESTINATARIO="[email protected]"
REMITENTE="[email protected]"

if systemctl is-active --quiet "$SERVICIO"; then

    echo "El servicio $SERVICIO está funcionando."

else

    /usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: Alerta de servicio detenido

ATENCIÓN:

El servicio $SERVICIO no se encuentra activo.

Equipo: $(hostname)
Fecha: $(date)

Revisa el estado del servicio.
EOF

    echo "Se generó una alerta por el servicio."

fi

9.1. Explicación de systemctl is-active

systemctl is-active --quiet "$SERVICIO"

systemctl administra y consulta unidades de systemd.

is-active comprueba si una unidad está activa.

--quiet evita mostrar el resultado en pantalla.

En lugar de imprimir información, podemos utilizar el código de salida para decidir qué hacer.

Si el servicio está activo, la condición será verdadera.

Si no está activo, se ejecutará el bloque else.

9.2. Explicación de date

$(date)

Ejecuta el comando date e inserta la fecha y hora en el mensaje.

Esto permite saber cuándo se generó la alerta.

¿Y si no tengo Apache?

Podemos cambiar:

SERVICIO="apache2"

por otro servicio instalado, como:

SERVICIO="ssh"

Debemos utilizar el nombre real de la unidad de systemd en nuestro equipo.

Importante: este script enviará una alerta cada vez que se ejecute mientras el servicio esté detenido. Para producción conviene aplicar una lógica de estado similar a la del ejemplo del disco.


10. Crear correos HTML desde Bash

Hasta ahora enviamos mensajes de texto plano.

Pero también podemos crear correos HTML con encabezados MIME.

Esto nos permitirá incluir títulos, colores, tablas y otros elementos visuales.

Creamos:

nano correo-html.sh

Agregamos:

#!/usr/bin/env bash

set -euo pipefail

DESTINATARIO="[email protected]"
REMITENTE="[email protected]"

/usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: Reporte HTML desde Debian
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Reporte del sistema</title>
</head>
<body style="font-family:Arial,sans-serif;background:#f4f4f4;padding:20px;">

    <div style="max-width:600px;margin:auto;background:#ffffff;padding:25px;border-radius:10px;">

        <h1 style="color:#167a42;">Reporte de Debian</h1>

        <p>Este correo fue generado automáticamente desde Bash.</p>

        <p><strong>Equipo:</strong> $(hostname)</p>

        <p><strong>Fecha:</strong> $(date)</p>

        <hr>

        <p style="color:#666666;font-size:13px;">
            Notificación automática del sistema.
        </p>

    </div>

</body>
</html>
EOF

10.1. ¿Qué es MIME?

MIME significa Multipurpose Internet Mail Extensions.

Permite describir diferentes tipos de contenido dentro de un mensaje de correo.

Por ejemplo:

  • Texto plano.
  • HTML.
  • Imágenes.
  • Documentos.
  • Archivos adjuntos.

10.2. MIME-Version

MIME-Version: 1.0

Indica que el mensaje utiliza el formato MIME.

10.3. Content-Type

Content-Type: text/html; charset=UTF-8

text/html indica que el cuerpo contiene HTML.

charset=UTF-8 establece la codificación de caracteres.

Esto permite representar letras acentuadas y otros caracteres, siempre que el mensaje se codifique y transporte correctamente.

10.4. Content-Transfer-Encoding

Content-Transfer-Encoding: 8bit

Indica que el cuerpo utiliza una codificación de transferencia de 8 bits.

Para este ejemplo utilizamos un cuerpo UTF-8 sencillo. En aplicaciones más complejas, conviene emplear una biblioteca MIME que seleccione y aplique correctamente la codificación.

10.5. ¿Por qué utilizamos estilos inline?

Los clientes de correo no interpretan CSS exactamente igual que los navegadores.

Muchos clientes restringen hojas de estilo externas y determinadas propiedades.

Por eso utilizamos:

style="color:#167a42;"

directamente en las etiquetas.

Esto mejora la compatibilidad, aunque no garantiza que todos los clientes muestren el mensaje de forma idéntica.


11. Enviar archivos adjuntos con Sendmail

Para enviar archivos adjuntos necesitamos construir un mensaje MIME de varias partes.

Podemos hacerlo manualmente, pero debemos respetar su estructura.

En este ejemplo adjuntaremos un archivo de texto.

Primero creamos un documento:

printf 'Reporte de prueba generado desde Debian.\n' > reporte.txt

Explicación

printf imprime texto con un formato controlado.

\n representa un salto de línea.

> redirige la salida hacia un archivo, reemplazando su contenido si ya existe.

reporte.txt es el archivo de destino.

Ahora creamos:

nano correo-adjunto.sh

Agregamos:

#!/usr/bin/env bash

set -euo pipefail

DESTINATARIO="[email protected]"
REMITENTE="[email protected]"

ARCHIVO="reporte.txt"
NOMBRE="reporte.txt"

if [[ ! -f "$ARCHIVO" ]]; then
    echo "Error: no existe el archivo $ARCHIVO"
    exit 1
fi

BOUNDARY="OPERSLINUX_$(date +%s)_${RANDOM}_${RANDOM}"

{
    printf 'To: %s\n' "$DESTINATARIO"
    printf 'From: %s\n' "$REMITENTE"
    printf 'Subject: Reporte adjunto desde Debian\n'
    printf 'MIME-Version: 1.0\n'
    printf 'Content-Type: multipart/mixed; boundary="%s"\n' "$BOUNDARY"
    printf '\n'

    printf -- '--%s\n' "$BOUNDARY"
    printf 'Content-Type: text/plain; charset=UTF-8\n'
    printf 'Content-Transfer-Encoding: 7bit\n'
    printf '\n'
    printf 'Se adjunta el reporte generado desde Debian.\n'
    printf '\n'

    printf -- '--%s\n' "$BOUNDARY"
    printf 'Content-Type: text/plain; name="%s"\n' "$NOMBRE"
    printf 'Content-Disposition: attachment; filename="%s"\n' "$NOMBRE"
    printf 'Content-Transfer-Encoding: base64\n'
    printf '\n'

    base64 -w 76 "$ARCHIVO"

    printf '\n'
    printf -- '--%s--\n' "$BOUNDARY"

} | /usr/sbin/sendmail -t -f "$REMITENTE"

echo "Mensaje con adjunto entregado a Sendmail."

11.1. ¿Qué significa multipart/mixed?

Content-Type: multipart/mixed

Indica que el mensaje contiene varias partes MIME.

En nuestro ejemplo:

  • Una parte de texto.
  • Una parte correspondiente al archivo adjunto.

11.2. ¿Qué es boundary?

BOUNDARY="OPERSLINUX_$(date +%s)_${RANDOM}_${RANDOM}"

La variable BOUNDARY contiene un identificador que separa las partes del mensaje.

date +%s muestra el tiempo Unix en segundos.

$RANDOM es una variable especial de Bash que produce un número pseudoaleatorio.

El identificador resultante se utiliza para delimitar las partes MIME.

En aplicaciones de producción debemos generar límites que no coincidan con el contenido de las partes y utilizar bibliotecas MIME cuando sea posible.

11.3. ¿Qué significa Content-Disposition?

Content-Disposition: attachment

Indica que esa parte debe tratarse como un archivo adjunto.

El atributo filename propone el nombre del archivo.

11.4. ¿Qué hace base64?

base64 -w 76 "$ARCHIVO"

base64 convierte los bytes del archivo a una representación textual Base64.

-w 76 ajusta las líneas a un máximo de 76 caracteres, como corresponde al formato MIME Base64.

"$ARCHIVO" indica el archivo que queremos codificar.

Base64 no cifra ni protege el contenido. Solamente transforma su representación.

11.5. ¿Por qué utilizamos printf –?

printf -- '--%s\n' "$BOUNDARY"

printf genera texto con formato.

-- indica el final de las opciones del comando.

Esto permite utilizar de forma segura un formato que comienza con guiones.

%s representa una cadena de texto.

\n representa un salto de línea.

11.6. ¿Por qué utilizamos llaves?

{
    ...
} | /usr/sbin/sendmail -t -f "$REMITENTE"

Las llaves agrupan varios comandos.

Toda la salida producida dentro del grupo se envía mediante la tubería | a Sendmail.

Así podemos construir el mensaje completo sin guardarlo previamente en otro archivo.

Limitaciones de este ejemplo

El ejemplo utiliza un archivo de texto ASCII con nombre fijo.

Para adjuntar PDF, imágenes y otros formatos debemos establecer el tipo MIME correspondiente, por ejemplo application/pdf, y asegurarnos de que las cabeceras y los nombres de archivo estén correctamente codificados.

El formato MIME requiere finales de línea CRLF durante la transmisión. Sendmail normalmente realiza la conversión correspondiente al enviar por SMTP, pero si construimos mensajes para otros transportes debemos tenerlo en cuenta.

Para sistemas reales que manejan archivos arbitrarios, caracteres internacionales y múltiples adjuntos, es preferible utilizar una biblioteca MIME.


12. Programar envíos automáticos con cron

Hasta ahora ejecutamos los scripts manualmente.

Ahora aprenderemos a programarlos.

Cron es un servicio que ejecuta comandos en horarios determinados.

Por ejemplo, podemos programar una comprobación de disco cada quince minutos.

12.1. Comprobar cron

Ejecutamos:

sudo systemctl status cron --no-pager

Si no está instalado:

sudo apt install cron

Después podemos iniciarlo:

sudo systemctl enable --now cron

Explicación

enable configura el servicio para iniciarse automáticamente al arrancar el sistema.

--now solicita iniciar el servicio inmediatamente, además de habilitarlo.

cron es el nombre del servicio.

12.2. Abrir nuestro crontab

Ejecutamos:

crontab -e

Explicación

crontab administra la programación de tareas del usuario.

-e abre el archivo de tareas programadas para editarlo.

Si es la primera vez, Debian puede solicitar que seleccionemos un editor.

12.3. Programar una alerta cada quince minutos

Agregamos:

*/15 * * * * /home/systemx/scripts-sendmail/alerta-disco-estado.sh

Debemos reemplazar /home/systemx por nuestra ruta real.

¿Qué significan los cinco campos?

┌──────── minuto
│ ┌────── hora
│ │ ┌──── día del mes
│ │ │ ┌── mes
│ │ │ │ ┌ día de la semana
│ │ │ │ │
*/15 * * * *

*/15 significa cada quince minutos dentro de cada hora.

Los cuatro asteriscos restantes permiten todas las horas, días del mes, meses y días de la semana.

Así, el script se ejecutará aproximadamente a los minutos 0, 15, 30 y 45 de cada hora.

Cron utiliza la zona horaria configurada para su entorno.

12.4. Comprobar las tareas programadas

Ejecutamos:

crontab -l

-l muestra las tareas del usuario actual.

12.5. Guardar un registro de ejecución

Podemos modificar nuestra tarea:

*/15 * * * * /home/systemx/scripts-sendmail/alerta-disco-estado.sh >> /home/systemx/scripts-sendmail/cron.log 2>&1

Explicación

>> agrega la salida estándar al final del archivo sin reemplazar su contenido anterior.

cron.log será nuestro registro.

2>&1 redirige la salida de errores, identificada con el descriptor 2, al mismo destino que la salida estándar, identificada con 1.

Así podremos revisar mensajes normales y errores.

Para consultar el archivo:

tail -n 30 ~/scripts-sendmail/cron.log

tail muestra las últimas líneas de un archivo.

-n 30 solicita las últimas treinta líneas.

Importante: los scripts ejecutados por cron tienen un entorno más limitado que nuestra terminal interactiva. Por eso conviene utilizar rutas absolutas para los programas y archivos.


13. Evitar ejecuciones simultáneas con flock

Si un script tarda demasiado, una nueva ejecución de cron podría comenzar antes de que termine la anterior.

Podemos utilizar flock para impedir ejecuciones simultáneas.

Por ejemplo:

*/15 * * * * /usr/bin/flock -n /home/systemx/scripts-sendmail/alerta-disco.lock /home/systemx/scripts-sendmail/alerta-disco-estado.sh

Explicación

flock administra bloqueos asociados a archivos.

-n solicita un bloqueo no bloqueante.

Si otra ejecución ya mantiene el bloqueo, el nuevo proceso no esperará indefinidamente.

alerta-disco.lock es el archivo utilizado para coordinar el bloqueo.

alerta-disco-estado.sh es el script que deseamos ejecutar.

Debemos asegurarnos de que el usuario tenga permisos para crear el archivo de bloqueo.

Esta técnica resulta útil cuando programamos comprobaciones frecuentes.


14. Automatizar una copia de seguridad y enviar una notificación

Otra aplicación práctica consiste en enviar un correo después de crear una copia de seguridad.

Crearemos un ejemplo sencillo utilizando tar.

Primero creamos:

nano respaldo-notificacion.sh

Agregamos:

#!/usr/bin/env bash

set -euo pipefail

ORIGEN="$HOME/Documentos"
DESTINO="$HOME/respaldos"

DESTINATARIO="[email protected]"
REMITENTE="[email protected]"

FECHA=$(date +%Y-%m-%d_%H-%M-%S)
ARCHIVO="$DESTINO/respaldo-$FECHA.tar.gz"

mkdir -p "$DESTINO"

if tar -czf "$ARCHIVO" -C "$HOME" "Documentos"; then

    /usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: Respaldo finalizado correctamente

La copia de seguridad terminó sin errores reportados por tar.

Archivo: $ARCHIVO

Fecha: $(date)
EOF

    echo "Respaldo creado: $ARCHIVO"

else

    /usr/sbin/sendmail -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: Error durante el respaldo

ATENCIÓN:

Ocurrió un error al crear el respaldo.

Origen: $ORIGEN

Fecha: $(date)
EOF

    echo "Error al crear el respaldo." >&2
    exit 1

fi

Este ejemplo supone que existe el directorio ~/Documentos.

14.1. Explicación de tar

tar -czf "$ARCHIVO" -C "$HOME" "Documentos"

tar permite agrupar archivos y directorios.

-c crea un archivo nuevo.

-z utiliza compresión gzip.

-f indica el nombre del archivo de salida.

"$ARCHIVO" contiene la ruta del respaldo.

-C "$HOME" cambia al directorio personal antes de seleccionar los archivos.

"Documentos" indica el directorio que queremos respaldar.

14.2. ¿Por qué utilizamos if directamente con tar?

if tar ...; then

Bash comprueba el código de salida de tar.

Si termina correctamente, ejecutamos el bloque de éxito.

Si falla, ejecutamos el bloque else.

Esto permite generar mensajes diferentes según el resultado.

Importante: que tar termine correctamente no garantiza que el respaldo pueda restaurarse ni que esté almacenado de forma segura. Debemos comprobar periódicamente la integridad y recuperación de nuestras copias.


15. Seguridad al automatizar correos

Automatizar mensajes es útil, pero debemos considerar varios aspectos.

15.1. No guardar contraseñas SMTP en los scripts

Si Sendmail ya está configurado mediante authinfo, nuestros scripts no necesitan conocer la contraseña SMTP.

Solamente entregan mensajes al agente local.

Esto reduce la exposición de credenciales.

15.2. No permitir destinatarios arbitrarios sin validación

Si nuestro script recibe direcciones desde formularios, archivos o argumentos externos, debemos validar su contenido.

Las cabeceras de correo son sensibles a saltos de línea y caracteres especiales.

Una entrada maliciosa podría intentar agregar cabeceras inesperadas.

15.3. Proteger los permisos

Podemos utilizar:

chmod 700 archivo.sh

para scripts privados.

Sin embargo, si contienen datos sensibles, también debemos proteger los directorios y archivos que utilizan.

15.4. Evitar alertas excesivas

Si una condición permanece activa, no debemos enviar cientos de mensajes idénticos.

Podemos utilizar archivos de estado, límites de frecuencia y bloqueos.

15.5. No enviar información confidencial sin necesidad

Los correos pueden almacenarse en distintos servidores y dispositivos.

No debemos incluir contraseñas, claves privadas ni datos sensibles innecesarios en nuestras notificaciones.

15.6. Revisar los límites del proveedor SMTP

Gmail y los servicios de hosting pueden aplicar límites de envío.

La automatización debe respetar esas restricciones.


16. Diagnosticar problemas en nuestros scripts

Si un script no envía mensajes, debemos comprobar cada etapa.

Paso 1. Revisar su sintaxis

bash -n enviar-correo.sh

bash ejecuta el intérprete.

-n comprueba la sintaxis sin ejecutar las instrucciones del script.

Esto ayuda a detectar errores de estructura.

Paso 2. Ejecutarlo con trazas

bash -x enviar-correo.sh

-x muestra las instrucciones conforme Bash las ejecuta, después de determinadas expansiones.

Es útil para depurar, pero puede revelar variables, rutas y datos sensibles.

No debemos publicar trazas sin revisarlas.

Paso 3. Comprobar el servicio

sudo systemctl status sendmail --no-pager

Paso 4. Revisar los registros

sudo journalctl -u sendmail -n 50 --no-pager

Paso 5. Consultar mensajes pendientes

sudo sendmail -bp

También podemos revisar la cola de envío local:

sudo sendmail -Ac -bp

Paso 6. Comprobar el resultado SMTP

Buscamos estados como:

stat=Sent

o:

stat=Deferred

Sent indica que Sendmail registró una entrega satisfactoria al siguiente servidor.

Deferred indica que la entrega quedó pendiente.

Debemos leer el mensaje completo para conocer el motivo.


17. Práctica final

Para reforzar lo aprendido, realizaremos las siguientes actividades.

Ejercicio 1: crear un script .sh que envíe un mensaje de bienvenida.

Ejercicio 2: modificar el script para recibir el destinatario como argumento.

Ejercicio 3: enviar una alerta cuando el disco supere un porcentaje configurado.

Ejercicio 4: comprobar si un servicio de systemd está activo.

Ejercicio 5: enviar un correo HTML con información del equipo.

Ejercicio 6: adjuntar un archivo de texto mediante MIME y Base64.

Ejercicio 7: programar una comprobación con cron.

Ejercicio 8: consultar los registros y comprobar que Sendmail aceptó el mensaje.

Podemos utilizar:

sudo journalctl -u sendmail --since "10 minutes ago" --no-pager

para observar los resultados recientes.


Conclusión

En esta quinta parte aprendimos a utilizar Bash y Sendmail para automatizar el envío de correos electrónicos desde Debian 13.

Creamos scripts con variables, argumentos y condiciones, desarrollamos alertas para vigilar el almacenamiento y los servicios, construimos mensajes HTML y aprendimos a adjuntar archivos mediante MIME.

También utilizamos cron para programar ejecuciones automáticas y vimos cómo prevenir notificaciones repetidas y ejecuciones simultáneas.

La principal ventaja de esta configuración es que nuestros scripts no necesitan conectarse directamente a Gmail ni administrar las credenciales SMTP.

Sendmail se encarga de procesar los mensajes y utilizar el servidor SMTP que configuramos previamente.

Esto nos permite desarrollar herramientas de administración más organizadas y reutilizables.

¿Qué aprenderemos en la Parte 6?

En la última entrega de esta serie aprenderemos a integrar Sendmail con aplicaciones desarrolladas en PHP puro.

Crearemos ejemplos prácticos para:

  • Enviar correos desde archivos .php.
  • Utilizar la función mail() de PHP.
  • Crear un formulario de contacto con HTML y PHP.
  • Validar los datos recibidos.
  • Enviar notificaciones desde una página web.
  • Construir correos HTML.
  • Evitar inyección de cabeceras y abuso del formulario.
  • Comprender la diferencia entre PHP mail(), Sendmail y una conexión SMTP directa.

Con ello podremos conectar nuestros conocimientos de Linux, Bash y administración de correo con el desarrollo web.

OPERSLINUX — Aprende Linux, servidores y programación mediante la práctica.

Deja un comentario