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:
- Crear un script Bash para enviar correos.
- Utilizar variables, argumentos y condiciones.
- Enviar alertas cuando el disco supere un porcentaje de uso.
- Comprobar servicios y enviar notificaciones.
- Crear correos HTML y adjuntar archivos mediante MIME.
- 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.



