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

Serie Postfix — Parte 2 | OPERSLINUX
En el tutorial anterior aprendimos a instalar y configurar Postfix en Debian 13 para enviar correos electrónicos mediante servidores SMTP externos, como HostGator y Gmail.
Ahora vamos a aprovechar esa configuración para desarrollar nuestros propios scripts en Bash y automatizar el envío de mensajes desde Linux.
A diferencia de un script que tiene el destinatario, remitente y mensaje escritos directamente en su código, en este tutorial crearemos una herramienta interactiva que nos preguntará qué queremos enviar, a quién, desde qué dirección, con qué asunto y si deseamos adjuntar algún archivo.
También aprenderemos a enviar mensajes HTML, generar alertas automáticas del sistema y programar notificaciones utilizando cron.
1. Requisitos previos
Para seguir este tutorial necesitaremos:
- Debian GNU/Linux 13.
- Postfix instalado y configurado con un servidor SMTP externo.
- Bash y acceso a la terminal.
- Una cuenta de correo autorizada para enviar mensajes.
- Conocimientos básicos sobre comandos de Linux.
Primero comprobamos que Postfix esté instalado:
dpkg -l | grep postfix
Después verificamos su configuración:
sudo postfix check
Consultamos el servidor SMTP configurado:
postconf relayhost
Por ejemplo, si utilizamos Gmail, podríamos encontrar:
relayhost = [smtp.gmail.com]:587
Si utilizamos HostGator, aparecerá el servidor SMTP correspondiente a nuestra cuenta.
También comprobaremos que existe la interfaz compatible con Sendmail:
ls -l /usr/sbin/sendmail
Aunque estamos utilizando Postfix, esta interfaz permite que los scripts entreguen mensajes al sistema de correo local.
2. ¿Cómo funciona el envío de correos desde Bash?
Nuestro programa utilizará el siguiente proceso:
USUARIO
|
v
SCRIPT BASH
|
| Solicita remitente,
| destinatario, asunto,
| mensaje y adjuntos
|
v
/usr/sbin/sendmail
|
v
POSTFIX
|
| SMTP autenticado y TLS
v
HOSTGATOR / GMAIL
|
v
DESTINATARIO
Bash se encargará de preparar el mensaje.
Postfix lo recibirá y lo enviará mediante el servidor SMTP que configuramos anteriormente.
Es importante recordar que la dirección del remitente debe estar autorizada por nuestro proveedor SMTP. El hecho de que nuestro programa permita escribir una dirección no significa que podamos utilizar identidades ajenas.
3. Crear nuestro primer correo desde Bash
Antes de desarrollar el programa interactivo, realizaremos una prueba sencilla.
Ejecutamos:
/usr/sbin/sendmail -t <<'EOF'
To: [email protected]
From: [email protected]
Subject: Prueba de Bash y Postfix
Content-Type: text/plain; charset=UTF-8
Hola.
Este mensaje fue creado desde Bash
y enviado mediante Postfix en Debian 13.
EOF
Debemos sustituir las direcciones por cuentas reales.
Explicación
/usr/sbin/sendmail es la interfaz compatible que Postfix proporciona para recibir mensajes locales.
-t indica que los destinatarios se obtendrán de las cabeceras del mensaje.
<<'EOF' inicia un documento heredado (heredoc) que permite enviar varias líneas de texto al comando.
To: identifica al destinatario.
From: identifica al remitente.
Subject: establece el asunto.
Content-Type: indica el formato del mensaje.
La línea vacía después de las cabeceras separa estas del contenido del correo.
4. Crear nuestro programa interactivo
Ahora desarrollaremos un programa que permita introducir los datos desde la terminal.
Su funcionamiento será parecido al siguiente:
=========================================
OPERSLINUX - CORREO BASH
=========================================
Correo del remitente:
> [email protected]
Correo del destinatario:
> [email protected]
Asunto:
> Prueba desde Debian 13
Tipo de mensaje:
1) Texto normal
2) HTML
Selecciona una opción:
> 1
Escribe el mensaje.
Para finalizar, escribe FIN en una línea:
Hola.
Este es un mensaje de prueba.
Saludos desde Debian 13.
FIN
¿Deseas adjuntar un archivo? (s/n):
> n
¿Enviar correo? (s/n):
> s
Mensaje aceptado por Postfix.
La ventaja es que podremos ejecutar el mismo script tantas veces como queramos, sin editar su código para cambiar los datos del correo.
5. Crear el archivo enviar-correo.sh
Primero creamos una carpeta para guardar nuestros scripts:
mkdir -p ~/scripts-postfix
Entramos:
cd ~/scripts-postfix
Creamos el archivo:
nano enviar-correo.sh
Agregamos el siguiente código:
#!/usr/bin/env bash
# ==========================================================
# OPERSLINUX
# Programa interactivo de correo con Bash y Postfix
# Debian GNU/Linux 13
# ==========================================================
set -o pipefail
# ----------------------------------------------------------
# 1. COMPROBAR DEPENDENCIAS
# ----------------------------------------------------------
for comando in base64 mktemp basename tr date; do
if ! command -v "$comando" >/dev/null 2>&1; then
printf 'Error: falta el comando %s\n' "$comando" >&2
exit 1
fi
done
if [[ ! -x /usr/sbin/sendmail ]]; then
echo "Error: no se encontró /usr/sbin/sendmail."
exit 1
fi
# ----------------------------------------------------------
# 2. VALIDAR DIRECCIONES DE CORREO
# ----------------------------------------------------------
validar_correo() {
local correo="$1"
[[ "$correo" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]
}
leer_correo() {
local etiqueta="$1"
local valor
while true; do
read -r -p "$etiqueta: " valor || return 1
if validar_correo "$valor"; then
printf '%s' "$valor"
return 0
fi
echo "Dirección no válida. Intenta nuevamente." >&2
done
}
# ----------------------------------------------------------
# 3. MOSTRAR ENCABEZADO
# ----------------------------------------------------------
echo
echo "=========================================="
echo " OPERSLINUX - CORREO BASH"
echo "=========================================="
echo
# ----------------------------------------------------------
# 4. SOLICITAR REMITENTE Y DESTINATARIO
# ----------------------------------------------------------
REMITENTE=$(leer_correo "Correo del remitente") || exit 1
DESTINATARIO=$(leer_correo "Correo del destinatario") || exit 1
echo
# ----------------------------------------------------------
# 5. SOLICITAR ASUNTO
# ----------------------------------------------------------
while true; do
read -r -p "Asunto: " ASUNTO || exit 1
if [[ -n "$ASUNTO" &&
"$ASUNTO" != *$'\r'* &&
"$ASUNTO" != *$'\n'* ]]; then
break
fi
echo "El asunto no puede estar vacío."
done
echo
# ----------------------------------------------------------
# 6. ELEGIR EL FORMATO
# ----------------------------------------------------------
echo "Tipo de mensaje:"
echo "1) Texto normal"
echo "2) HTML"
echo
while true; do
read -r -p "Selecciona una opción [1-2]: " OPCION || exit 1
case "$OPCION" in
1)
TIPO="text/plain"
NOMBRE_TIPO="Texto normal"
break
;;
2)
TIPO="text/html"
NOMBRE_TIPO="HTML"
break
;;
*)
echo "Selecciona 1 o 2."
;;
esac
done
# ----------------------------------------------------------
# 7. CREAR ARCHIVOS TEMPORALES
# ----------------------------------------------------------
umask 077
MENSAJE_TMP=$(mktemp) || exit 1
CORREO_TMP=$(mktemp) || {
rm -f -- "$MENSAJE_TMP"
exit 1
}
trap 'rm -f -- "$MENSAJE_TMP" "$CORREO_TMP"' EXIT
# ----------------------------------------------------------
# 8. SOLICITAR EL CONTENIDO
# ----------------------------------------------------------
echo
echo "Escribe tu mensaje."
echo "Para finalizar, escribe FIN en una línea."
echo
while IFS= read -r LINEA; do
if [[ "$LINEA" == "FIN" ]]; then
break
fi
printf '%s\n' "$LINEA" >> "$MENSAJE_TMP"
done
if [[ ! -s "$MENSAJE_TMP" ]]; then
echo "Error: el mensaje está vacío."
exit 1
fi
# ----------------------------------------------------------
# 9. SOLICITAR ARCHIVO ADJUNTO
# ----------------------------------------------------------
ADJUNTO=""
echo
read -r -p "¿Deseas adjuntar un archivo? (s/n): " RESPUESTA || exit 1
if [[ "$RESPUESTA" =~ ^[sS]$ ]]; then
read -r -p "Ruta del archivo: " ADJUNTO || exit 1
if [[ ! -f "$ADJUNTO" || ! -r "$ADJUNTO" ]]; then
echo "Error: el archivo no existe o no puede leerse."
exit 1
fi
fi
# ----------------------------------------------------------
# 10. MOSTRAR RESUMEN
# ----------------------------------------------------------
echo
echo "=========================================="
echo " RESUMEN"
echo "=========================================="
echo "Remitente: $REMITENTE"
echo "Destinatario: $DESTINATARIO"
echo "Asunto: $ASUNTO"
echo "Formato: $NOMBRE_TIPO"
echo "Adjunto: ${ADJUNTO:-Ninguno}"
echo "=========================================="
echo
read -r -p "¿Enviar correo? (s/n): " CONFIRMAR || exit 1
if [[ ! "$CONFIRMAR" =~ ^[sS]$ ]]; then
echo "Envío cancelado."
exit 0
fi
# ----------------------------------------------------------
# 11. CONSTRUIR EL MENSAJE MIME
# ----------------------------------------------------------
ASUNTO_B64=$(printf '%s' "$ASUNTO" | base64 -w 0)
BOUNDARY="OPERSLINUX_$(date +%s)_$$_${RANDOM}"
{
printf 'To: %s\n' "$DESTINATARIO"
printf 'From: %s\n' "$REMITENTE"
printf 'Subject: =?UTF-8?B?%s?=\n' "$ASUNTO_B64"
printf 'MIME-Version: 1.0\n'
if [[ -n "$ADJUNTO" ]]; then
printf 'Content-Type: multipart/mixed; boundary="%s"\n' "$BOUNDARY"
printf '\n'
printf -- '--%s\n' "$BOUNDARY"
printf 'Content-Type: %s; charset=UTF-8\n' "$TIPO"
printf 'Content-Transfer-Encoding: base64\n'
printf '\n'
base64 -w 76 "$MENSAJE_TMP"
printf '\n'
NOMBRE_ARCHIVO=$(basename -- "$ADJUNTO")
NOMBRE_SEGURO=$(printf '%s' "$NOMBRE_ARCHIVO" |
LC_ALL=C tr -c 'A-Za-z0-9._-' '_')
printf -- '--%s\n' "$BOUNDARY"
printf 'Content-Type: application/octet-stream\n'
printf 'Content-Transfer-Encoding: base64\n'
printf 'Content-Disposition: attachment; filename="%s"\n' "$NOMBRE_SEGURO"
printf '\n'
base64 -w 76 "$ADJUNTO"
printf '\n'
printf -- '--%s--\n' "$BOUNDARY"
else
printf 'Content-Type: %s; charset=UTF-8\n' "$TIPO"
printf 'Content-Transfer-Encoding: base64\n'
printf '\n'
base64 -w 76 "$MENSAJE_TMP"
printf '\n'
fi
} > "$CORREO_TMP"
# ----------------------------------------------------------
# 12. ENTREGAR EL MENSAJE A POSTFIX
# ----------------------------------------------------------
echo
echo "Entregando mensaje a Postfix..."
if /usr/sbin/sendmail -oi -t -f "$REMITENTE" < "$CORREO_TMP"; then
echo "Mensaje aceptado por el sistema local."
echo "Consulta los registros para verificar su entrega."
else
echo "Error: no fue posible entregar el mensaje a Postfix."
exit 1
fi
Nota: este programa es una herramienta educativa para uso local. La validación de direcciones es básica y no cubre todos los formatos admitidos por los estándares de correo. Para producción conviene añadir límites de tamaño, controles de frecuencia y una implementación MIME más completa.
6. Explicación de los elementos principales del script
6.1. ¿Qué significa #!/usr/bin/env bash?
#!/usr/bin/env bash
Esta línea se conoce como shebang.
Indica que el archivo debe ejecutarse utilizando el intérprete Bash.
6.2. ¿Para qué sirve read?
read -r -p "Asunto: " ASUNTO
El comando read permite solicitar información al usuario.
El parámetro -r evita que las barras invertidas sean interpretadas como caracteres especiales.
El parámetro -p muestra un mensaje antes de solicitar la entrada.
La variable ASUNTO almacena el valor introducido.
6.3. ¿Qué son las variables?
Una variable permite almacenar información.
Por ejemplo:
DESTINATARIO="[email protected]"
Para consultar su contenido:
echo "$DESTINATARIO"
Las comillas dobles ayudan a conservar correctamente los espacios y caracteres del valor.
6.4. ¿Cómo funcionan las condiciones?
Nuestro programa utiliza condiciones como:
if [[ -f "$ADJUNTO" ]]; then
echo "El archivo existe."
fi
if inicia la condición.
[[ ... ]] evalúa una expresión.
-f comprueba si existe un archivo regular.
then indica qué instrucciones ejecutar.
fi finaliza la condición.
6.5. ¿Cómo funcionan los ciclos?
También utilizamos:
while true; do
read -r -p "Asunto: " ASUNTO
[[ -n "$ASUNTO" ]] && break
done
Este ciclo solicita un asunto hasta que el usuario introduzca un valor no vacío.
while permite repetir instrucciones.
do inicia el bloque.
done lo finaliza.
break interrumpe el ciclo.
7. Dar permisos y ejecutar el programa
Guardamos el archivo y ejecutamos:
chmod 700 enviar-correo.sh
Esto permite que el propietario lea, modifique y ejecute el script, mientras que los demás usuarios no tendrán permisos.
Ahora lo ejecutamos:
./enviar-correo.sh
El programa solicitará los datos del correo.
8. Primera práctica: enviar un mensaje de texto
Ejecutamos:
./enviar-correo.sh
Introducimos el remitente y destinatario.
Después escribimos el asunto.
Seleccionamos:
1) Texto normal
Escribimos:
Hola.
Este es mi primer correo interactivo
utilizando Bash y Postfix.
Saludos desde Debian 13.
FIN
Cuando el programa pregunte si queremos adjuntar un archivo, respondemos n.
Finalmente confirmamos el envío.
Si el mensaje es aceptado por Postfix, el programa mostrará una confirmación local.
Para comprobar el transporte:
postqueue -p
También consultamos los registros:
sudo journalctl --since "10 minutes ago" --no-pager | grep -i postfix
9. Segunda práctica: enviar un correo HTML
Los mensajes HTML permiten incorporar títulos, colores, tablas y enlaces.
Ejecutamos nuevamente:
./enviar-correo.sh
Seleccionamos:
2) HTML
Introducimos:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Correo de prueba</title>
</head>
<body style="font-family: Arial, sans-serif;">
<h1 style="color: #008a45;">
Notificación desde Debian 13
</h1>
<p>
Este mensaje fue enviado utilizando
<strong>Bash y Postfix</strong>.
</p>
<p>
Saludos desde OPERSLINUX.
</p>
</body>
</html>
FIN
El script establecerá el tipo de contenido:
Content-Type: text/html; charset=UTF-8
Esto permite que el cliente de correo interprete el mensaje como HTML.
Para correos electrónicos es recomendable utilizar estilos sencillos en línea, ya que algunos clientes de correo no admiten todas las características de CSS.
10. Tercera práctica: enviar un archivo adjunto
Primero creamos un archivo de prueba:
printf 'Reporte de prueba\nServidor Debian 13\n' > reporte.txt
Ejecutamos:
./enviar-correo.sh
Cuando aparezca:
¿Deseas adjuntar un archivo? (s/n):
Respondemos:
s
Indicamos la ruta:
./reporte.txt
El programa comprobará que el archivo exista y sea legible.
Después lo incorporará al mensaje utilizando MIME.
¿Qué es Base64?
Base64 permite representar datos binarios mediante caracteres de texto.
En nuestro programa utilizamos:
base64 -w 76 "$ADJUNTO"
Esto codifica el archivo y divide la salida en líneas de hasta 76 caracteres.
El cliente de correo podrá interpretar esa sección como un archivo adjunto.
Debemos recordar que Base64 aumenta el tamaño de los datos aproximadamente un 33 %, por lo que conviene evitar archivos demasiado grandes.
11. ¿Qué es MIME?
MIME significa Multipurpose Internet Mail Extensions.
Permite incorporar diferentes tipos de contenido dentro de un correo electrónico.
Por ejemplo:
MENSAJE MIME
│
├── Cabeceras
│
├── Contenido HTML
│
└── Archivo adjunto
Cuando un mensaje contiene varias partes utilizamos:
Content-Type: multipart/mixed
Las partes se separan mediante un identificador llamado boundary.
Nuestro script genera ese identificador automáticamente.
12. Crear alertas automáticas del almacenamiento
Hasta ahora hemos desarrollado un programa interactivo.
Pero Bash también permite enviar correos automáticamente cuando ocurre determinada condición.
Por ejemplo, podemos crear una alerta cuando el almacenamiento de nuestro servidor supere el 80 %.
Creamos:
nano alerta-disco.sh
Agregamos:
#!/usr/bin/env bash
# ==========================================
# OPERSLINUX - ALERTA DE DISCO
# ==========================================
DESTINATARIO="[email protected]"
REMITENTE="[email protected]"
LIMITE=80
USO=$(df -P / | awk 'NR==2 {
gsub("%", "", $5);
print $5
}')
if [[ ! "$USO" =~ ^[0-9]+$ ]]; then
echo "Error al consultar el uso del disco." >&2
exit 1
fi
if (( USO >= LIMITE )); then
ASUNTO="Alerta: almacenamiento al ${USO}%"
/usr/sbin/sendmail -oi -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: $ASUNTO
Content-Type: text/plain; charset=UTF-8
ALERTA DEL SISTEMA
La partición raíz ha alcanzado el ${USO}%.
Límite configurado: ${LIMITE}%.
Servidor: $(hostname)
Revisa el espacio disponible.
EOF
fi
Debemos sustituir las direcciones por cuentas reales.
Explicación
La instrucción:
df -P /
consulta el uso de almacenamiento del sistema de archivos que contiene /.
Después:
awk 'NR==2 {gsub("%", "", $5); print $5}'
extrae el porcentaje de uso.
Si el valor es igual o superior al límite, el script construye y envía una alerta.
Por ejemplo:
Uso actual: 85%
Límite: 80%
Resultado: enviar alerta.
Si el uso es inferior al límite, no enviará ningún mensaje.
13. Programar las alertas con cron
Cron permite ejecutar tareas periódicamente sin intervención del usuario.
Comprobamos si está instalado:
systemctl status cron --no-pager
Si no lo tenemos:
sudo apt install cron
Lo habilitamos:
sudo systemctl enable --now cron
Damos permisos al script:
chmod 700 alerta-disco.sh
Abrimos la configuración de cron:
crontab -e
Agregamos:
*/30 * * * * /home/usuario/scripts-postfix/alerta-disco.sh
Debemos sustituir la ruta por la ubicación real del archivo.
¿Qué significa */30 * * * *?
Los cinco campos representan:
MINUTO HORA DÍA MES DÍA_SEMANA
La expresión */30 en el campo de minutos indica que el programa se ejecutará cada 30 minutos.
Si el almacenamiento continúa por encima del límite, el script enviará una alerta en cada ejecución. Para producción conviene agregar un control que evite notificaciones repetitivas.
14. Enviar notificaciones de respaldos
Otra aplicación útil es enviar un correo cuando termina una copia de seguridad.
Creamos:
nano respaldo-notificacion.sh
Agregamos:
#!/usr/bin/env bash
DESTINATARIO="[email protected]"
REMITENTE="[email protected]"
DESTINO="$HOME/Respaldos"
mkdir -p "$DESTINO" || exit 1
ARCHIVO="$DESTINO/respaldo-$(date +%F).tar.gz"
if tar -czf "$ARCHIVO" -C "$HOME" Documentos; then
ESTADO="CORRECTO"
DETALLE="El respaldo finalizó correctamente."
else
ESTADO="ERROR"
DETALLE="El respaldo presentó un problema."
fi
/usr/sbin/sendmail -oi -t -f "$REMITENTE" <<EOF
To: $DESTINATARIO
From: $REMITENTE
Subject: Respaldo - $ESTADO
Content-Type: text/plain; charset=UTF-8
NOTIFICACIÓN DE RESPALDO
Estado: $ESTADO
$DETALLE
Archivo previsto:
$ARCHIVO
Fecha: $(date)
Servidor: $(hostname)
EOF
Este ejemplo supone que existe la carpeta ~/Documentos.
Debemos ajustar las rutas y direcciones a nuestro sistema.
También conviene comprobar la integridad de los respaldos y conservar copias fuera del servidor original.
15. Recomendaciones de seguridad
Al utilizar Bash y Postfix debemos considerar algunos aspectos.
No guardar contraseñas SMTP en los scripts. Postfix ya tiene configuradas las credenciales necesarias para conectarse al proveedor.
Utilizar remitentes autorizados. El programa interactivo permite escribir el remitente, pero debemos respetar las identidades permitidas por nuestro servidor SMTP.
Validar los datos. Las cabeceras y direcciones deben revisarse para reducir errores y riesgos de inyección.
Limitar los adjuntos. Los archivos grandes pueden superar las restricciones del proveedor y consumir recursos innecesariamente.
Proteger los scripts. Podemos utilizar permisos restrictivos cuando contengan rutas, configuraciones o información privada.
Evitar envíos repetitivos. Las alertas programadas deben incorporar controles para no saturar los buzones.
16. Solucionar problemas frecuentes
El correo no llega
Consultamos la cola:
postqueue -p
Después revisamos los registros:
sudo journalctl --since "15 minutes ago" --no-pager | grep -i postfix
No existe /usr/sbin/sendmail
Comprobamos:
ls -l /usr/sbin/sendmail
Y:
dpkg -l | grep postfix
Si Postfix no está instalado, debemos completar el primer tutorial de la serie.
Permission denied
Puede indicar que el script no tiene permisos de ejecución.
Ejecutamos:
chmod 700 enviar-correo.sh
Después:
./enviar-correo.sh
El correo HTML aparece como texto
Debemos comprobar que el mensaje tenga la cabecera:
Content-Type: text/html; charset=UTF-8
También debemos verificar que exista una línea vacía entre las cabeceras y el contenido.
El adjunto no aparece
Comprobamos que el archivo exista:
ls -lh reporte.txt
También debemos verificar que el usuario tenga permisos para leerlo y que la estructura MIME sea válida.
Cron no ejecuta el script
Comprobamos:
systemctl status cron --no-pager
Y:
crontab -l
Debemos utilizar rutas absolutas y recordar que cron puede ejecutar los programas con un entorno más limitado que nuestra terminal habitual.
17. Ejercicios para practicar
Una vez que el programa funcione podemos realizar las siguientes prácticas:
- Enviar un correo de texto utilizando el programa interactivo.
- Enviar un mensaje HTML con títulos, colores y enlaces.
- Adjuntar un archivo PDF o CSV.
- Crear una alerta de almacenamiento y comprobar su funcionamiento.
- Programar una tarea mediante cron.
- Desarrollar un script que avise cuando un servicio deje de funcionar.
Estos ejercicios permiten aplicar variables, condiciones, ciclos, lectura de datos y automatización en situaciones reales.
Conclusión
En esta segunda parte de nuestra serie sobre Postfix aprendimos a utilizar Bash para enviar correos electrónicos desde Debian 13.
Comenzamos con una prueba sencilla mediante la interfaz compatible con Sendmail y después desarrollamos un programa interactivo que solicita el remitente, destinatario, asunto, mensaje y archivo adjunto.
También aprendimos a construir mensajes HTML, utilizar MIME, generar alertas del sistema y programar tareas mediante cron.
La ventaja principal es que Bash se encarga de preparar el contenido y Postfix se encarga del transporte del correo.
Gracias a esta separación podemos crear herramientas de automatización sin almacenar contraseñas SMTP dentro de nuestros scripts.
En la siguiente parte de nuestra serie aprenderemos a utilizar PHP puro con Postfix para desarrollar formularios HTML, validar información y enviar mensajes desde aplicaciones web.
OPERSLINUX — Aprende Linux, servidores y programación mediante la práctica.



