Módulo 3: Deployment Automation

7. Notificaciones de despliegue

Qué cubre esta cápsula

Tu pipeline corre tests, buildea imagen, aplica migrations, deploya, y verifica health. Cuando termina, alguien tiene que saberlo. Hoy el "alguien" eres tú — vas a GitHub Actions, ves el ✅ o ❌, y lo comunicas manualmente al equipo en Slack. "Hey, deployé la feature X" o "Ojo, el deploy a prod falló".

Esta cápsula automatiza esa comunicación con webhooks a Slack o Discord. Cuando el deploy termina, un mensaje aparece automáticamente en el canal del equipo: success o failure, el commit y autor, la versión deployada, link al run. Cero intervención humana. Cero "¿alguien deployó algo?" en Slack.

Al terminar, podrás:

  • Configurar un Slack webhook o Discord webhook para notificaciones del deploy
  • Implementar notificaciones diferentes para success vs failure
  • Incluir contexto útil: commit SHA, mensaje, autor, link al PR, link al run
  • Manejar notificaciones cuando el job falla (no solo cuando "success")
  • Decidir qué notificar y qué silenciar — evita spam
  • Diagnosticar webhooks que no llegan o se ven mal

El problema: comunicación manual del deploy

Sin notificaciones automáticas:

[GitHub Actions: deploy successful]
        ↓
Tú: vas al canal #deploys en Slack
     "Hey equipo, deployé v1.2.3 a producción ✅"
        ↓
Compañera A: "Gracias"
Compañera B: "¿Qué incluye?"
Tú: "Ah, dejame buscar el PR..."
[5 minutos buscando el link]
Tú: "Acá: github.com/repo/pull/42"
        ↓
[20 minutos después, deploy a prod automático en CI por otro PR]
[Nadie se entera porque tú te fuiste a almorzar]
[2 horas después]
Compañera C: "¿Alguien sabe si deploydeon? La feature X ya está en prod?"

Lo que falta es un patrón: que la comunicación del deploy sea automática, consistente, y con todo el contexto que el equipo necesita.


El modelo mental: el pipeline como bot del equipo

Piensa el pipeline como un bot que postea en Slack/Discord automáticamente:

[Pipeline termina]
        ↓
   ¿Success o failure?
   /              \
  ✅                ❌
  │                │
  ▼                ▼
[Mensaje Slack]  [Mensaje Slack]
"🚀 Deploy v1.2.3" "🚨 Deploy FAILED"
"Author: alice"   "Author: bob"
"Commit: abc1234" "Commit: xyz9876"
"PR: #42"         "PR: #43"
"Run: actions/..." "Run: actions/..."
"Time: 3m 24s"    "Step failed: migration"
"App: deploy.url" "Logs: see run"

El equipo entero está sincronizado en tiempo real con producción, sin que nadie tenga que recordar postear.


Step 1: configurar Slack webhook

Si tu equipo usa Slack:

  1. Crear app de Slack:

    • Ir a api.slack.com/apps
    • Click Create New AppFrom scratch
    • Nombre: "GitHub Deploy Notifications"
    • Workspace: el de tu equipo
  2. Activar Incoming Webhooks:

    • En la app → FeaturesIncoming Webhooks → toggle On
    • Click Add New Webhook to Workspace
    • Elige el canal: #deploys (recomendado crear uno dedicado)
    • Autoriza
  3. Copiar el webhook URL:

    • Formato: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
    • Este URL es secreto: cualquiera con él puede postear al canal
  4. Agregar a GitHub Secrets:

    • GitHub → Settings → Secrets → New repository secret
    • Name: SLACK_WEBHOOK_URL
    • Value: el URL copiado

Step 1 alternativo: configurar Discord webhook

Si tu equipo usa Discord:

  1. En tu server de Discord:

    • Click derecho en el canal #deploysEdit Channel
    • IntegrationsWebhooksNew Webhook
    • Nombre: "Deploy Bot"
    • Click Copy Webhook URL
  2. Formato del URL:

    • https://discord.com/api/webhooks/000000000000000000/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  3. Agregar a GitHub Secrets:

    • Name: DISCORD_WEBHOOK_URL
    • Value: el URL copiado

Step 2: notificación de success al final del deploy

Agrega al final del job deploy:

- name: Notify Slack on success
  if: success()
  run: |
    curl -X POST -H 'Content-type: application/json' \
      --data '{
        "blocks": [
          {
            "type": "header",
            "text": {
              "type": "plain_text",
              "text": "🚀 Deploy Successful"
            }
          },
          {
            "type": "section",
            "fields": [
              {
                "type": "mrkdwn",
                "text": "*Repository:*\n${{ github.repository }}"
              },
              {
                "type": "mrkdwn",
                "text": "*Branch:*\n${{ github.ref_name }}"
              },
              {
                "type": "mrkdwn",
                "text": "*Author:*\n${{ github.actor }}"
              },
              {
                "type": "mrkdwn",
                "text": "*Commit:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>"
              }
            ]
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Message:* ${{ github.event.head_commit.message }}"
            }
          },
          {
            "type": "actions",
            "elements": [
              {
                "type": "button",
                "text": { "type": "plain_text", "text": "View App" },
                "url": "${{ vars.DEPLOY_URL }}"
              },
              {
                "type": "button",
                "text": { "type": "plain_text", "text": "View Run" },
                "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
              }
            ]
          }
        ]
      }' \
      ${{ secrets.SLACK_WEBHOOK_URL }}

Esto envía un mensaje rico con:

  • Header destacando "Deploy Successful"
  • Bloque con fields (repo, branch, author, commit SHA con link)
  • Mensaje del commit
  • Botones para ir a la app o al run

En Slack se ve así:

🚀 Deploy Successful

Repository:        Branch:
mikenieva/myapp    main

Author:            Commit:
mikenieva          abc1234 [link]

Message: feat: add user profile endpoint

[ View App ]  [ View Run ]

Click en "View App" abre tu producción. Click en "View Run" abre el run de GitHub Actions.


Step 3: notificación de failure

El problema con if: success() es que solo notifica cuando todo pasó. ¿Qué pasa si el deploy falla? Nadie se entera.

Agrega una notificación separada para failure:

- name: Notify Slack on failure
  if: failure()
  run: |
    curl -X POST -H 'Content-type: application/json' \
      --data '{
        "blocks": [
          {
            "type": "header",
            "text": {
              "type": "plain_text",
              "text": "🚨 Deploy FAILED"
            }
          },
          {
            "type": "section",
            "fields": [
              {
                "type": "mrkdwn",
                "text": "*Repository:*\n${{ github.repository }}"
              },
              {
                "type": "mrkdwn",
                "text": "*Author:*\n${{ github.actor }}"
              },
              {
                "type": "mrkdwn",
                "text": "*Commit:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>"
              },
              {
                "type": "mrkdwn",
                "text": "*Failed Job:*\n${{ github.job }}"
              }
            ]
          },
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": ":warning: Investigate immediately. App may be in inconsistent state."
            }
          },
          {
            "type": "actions",
            "elements": [
              {
                "type": "button",
                "text": { "type": "plain_text", "text": "View Failed Run" },
                "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
                "style": "danger"
              }
            ]
          }
        ]
      }' \
      ${{ secrets.SLACK_WEBHOOK_URL }}

Diferencias:

  • if: failure(): corre solo cuando el job falló (en cualquier step previo)
  • Header rojo "Deploy FAILED"
  • Warning explícito: "Investigate immediately"
  • Botón danger style: visualmente distinto, en rojo

En Slack aparece destacado, imposible de ignorar.


Step 4: notificación a Discord (alternativa)

Si usas Discord, el formato JSON es distinto:

- name: Notify Discord on success
  if: success()
  run: |
    curl -X POST -H "Content-Type: application/json" \
      --data '{
        "embeds": [{
          "title": "🚀 Deploy Successful",
          "color": 3066993,
          "fields": [
            {"name": "Repository", "value": "${{ github.repository }}", "inline": true},
            {"name": "Branch", "value": "${{ github.ref_name }}", "inline": true},
            {"name": "Author", "value": "${{ github.actor }}", "inline": true},
            {"name": "Commit", "value": "[${{ github.sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})", "inline": false},
            {"name": "Message", "value": "${{ github.event.head_commit.message }}", "inline": false}
          ],
          "footer": {
            "text": "Click for details"
          },
          "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
          "timestamp": "${{ github.event.head_commit.timestamp }}"
        }]
      }' \
      ${{ secrets.DISCORD_WEBHOOK_URL }}

Discord usa "embeds" con color hex (3066993 = verde, 15158332 = rojo).


Manejo de comillas y caracteres especiales

Un problema sutil: los commit messages pueden contener ", \, \n, emojis. Esto rompe el JSON.

Ejemplo problemático:

# commit message: feat: add "premium" filter
# JSON resultante:
"text": "*Message:* feat: add "premium" filter"
                              ^ rompe el JSON

Solución 1: usar jq para escapar correctamente

- name: Build Slack payload
  id: payload
  run: |
    MESSAGE=$(echo "${{ github.event.head_commit.message }}" | head -1)
    PAYLOAD=$(jq -n \
      --arg message "$MESSAGE" \
      --arg sha "${{ github.sha }}" \
      --arg author "${{ github.actor }}" \
      '{
        text: "Deploy Successful",
        blocks: [{
          type: "section",
          text: { type: "mrkdwn", text: "*Message:* \($message)" }
        }, {
          type: "section",
          text: { type: "mrkdwn", text: "*Author:* \($author)\n*Commit:* \($sha)" }
        }]
      }')
    echo "payload<<EOF" >> $GITHUB_OUTPUT
    echo "$PAYLOAD" >> $GITHUB_OUTPUT
    echo "EOF" >> $GITHUB_OUTPUT

- name: Send Slack notification
  run: |
    curl -X POST -H 'Content-type: application/json' \
      --data '${{ steps.payload.outputs.payload }}' \
      ${{ secrets.SLACK_WEBHOOK_URL }}

jq escapa automáticamente comillas, backslashes, y caracteres especiales.

Solución 2: usar action de tercero

- name: Slack notification
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "Deploy successful",
        "blocks": [...]
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

La action maneja escaping automáticamente.


Workflow completo con notificaciones

deploy:
  name: Deploy
  runs-on: ubuntu-latest
  needs: build-and-push
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'

  steps:
    - uses: actions/checkout@v4

    - uses: actions/setup-python@v5
      with:
        python-version: '3.12'

    - run: pip install -e ".[dev]"

    - name: Run migrations
      env:
        DATABASE_URL: ${{ secrets.DATABASE_URL }}
      run: alembic upgrade head

    - name: Redeploy Railway
      env:
        RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
      run: |
        npm install -g @railway/cli
        railway redeploy --service ${{ secrets.RAILWAY_SERVICE_ID }}

    - name: Health check
      run: |
        DEPLOY_URL="${{ vars.DEPLOY_URL }}"
        for i in $(seq 1 24); do
          CODE=$(curl -s -o /dev/null -w "%{http_code}" "$DEPLOY_URL/health" || echo "000")
          [ "$CODE" = "200" ] && exit 0
          sleep 5
        done
        exit 1

    # ─── Notifications ───
    - name: Notify Slack on success
      if: success()
      uses: slackapi/slack-github-action@v1
      with:
        payload: |
          {
            "blocks": [
              {
                "type": "header",
                "text": {"type": "plain_text", "text": "🚀 Deploy Successful"}
              },
              {
                "type": "section",
                "fields": [
                  {"type": "mrkdwn", "text": "*Repo:*\n${{ github.repository }}"},
                  {"type": "mrkdwn", "text": "*Author:*\n${{ github.actor }}"},
                  {"type": "mrkdwn", "text": "*Branch:*\n${{ github.ref_name }}"},
                  {"type": "mrkdwn", "text": "*Commit:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>"}
                ]
              },
              {
                "type": "actions",
                "elements": [
                  {"type": "button", "text": {"type": "plain_text", "text": "View App"}, "url": "${{ vars.DEPLOY_URL }}"},
                  {"type": "button", "text": {"type": "plain_text", "text": "View Run"}, "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}
                ]
              }
            ]
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

    - name: Notify Slack on failure
      if: failure()
      uses: slackapi/slack-github-action@v1
      with:
        payload: |
          {
            "blocks": [
              {
                "type": "header",
                "text": {"type": "plain_text", "text": "🚨 Deploy FAILED"}
              },
              {
                "type": "section",
                "text": {"type": "mrkdwn", "text": ":warning: *Author:* ${{ github.actor }}\n*Commit:* <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>\n*Failed step:* check the run"}
              },
              {
                "type": "actions",
                "elements": [
                  {"type": "button", "text": {"type": "plain_text", "text": "View Failed Run"}, "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", "style": "danger"}
                ]
              }
            ]
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

Qué notificar y qué silenciar

Tentas de notificar todo: cada PR, cada CI run, cada deploy. Resultado: el canal se vuelve ruido. El equipo aprende a ignorarlo.

Qué notificar (señal alta):

  • ✅ Deploy a producción exitoso (lo que estamos haciendo)
  • ❌ Deploy a producción fallido
  • ❌ CI failure en main (alguien rompió main)
  • ⚠️ Migration que tarda inusualmente (>5 min)
  • 🔄 Rollback ejecutado

Qué NO notificar (ruido):

  • ❌ Cada CI run de feature branches (mucho ruido)
  • ❌ Cada PR abierto (ya hay otro canal para PRs)
  • ❌ Cada commit individual a feature branches

Regla: notifica eventos que el equipo necesita actuar sobre ellos o que representan cambios al estado de producción. Todo lo demás es ruido.


Canales separados por severidad

Equipo maduro usa múltiples canales:

#deploys                  → cada deploy a prod (success + failure)
#alerts                   → solo failures + incidents
#ci-failures-main         → cuando main rompe (CI rojo en main)
#deploy-archive (silent) → audit log de todos los deploys (no notifications)

Cada canal tiene un webhook distinto. En GitHub Secrets:

  • SLACK_WEBHOOK_DEPLOYS
  • SLACK_WEBHOOK_ALERTS
  • SLACK_WEBHOOK_ARCHIVE

El workflow usa el correcto según contexto:

- name: Notify alerts on failure
  if: failure()
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ALERTS }}  # canal de alerts
  run: ...

- name: Notify deploys always
  if: always()
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_DEPLOYS }} # canal de deploys
  run: ...

Trampas comunes

1. Webhook URL leaked en el output

Síntoma: alguien ve el webhook URL en logs de Actions y lo usa para postear spam.

Causa: el URL aparece en algún log step (e.g., echo $SLACK_WEBHOOK_URL).

Cómo manejar: GitHub Secrets se enmascaran automáticamente en logs (***), pero solo si los referencias exactamente. Si construyes variantes (substring), no se enmascaran:

# ✅ Bien: GitHub enmascara $SLACK_WEBHOOK_URL en logs
- run: curl ... ${{ secrets.SLACK_WEBHOOK_URL }}

# ❌ Mal: substring de un secret no se enmascara
- run: echo "Webhook starts with: ${SLACK_WEBHOOK_URL:0:20}"

Nunca printees secrets, ni siquiera substrings.

2. Notificación en cada PR ruidoso

Síntoma: el canal #deploys recibe 50 mensajes/día porque notifica cada CI run.

Causa: el step de notificación no tiene if: github.ref == 'refs/heads/main' o el job no es solo de deploy.

Cómo manejar: notificaciones solo en deploys (job deploy), que ya tiene if: github.event_name == 'push' && github.ref == 'refs/heads/main'. CI runs en PRs no notifican.

3. JSON inválido por caracteres especiales

Síntoma: webhook retorna 400 Bad Request: invalid_payload.

Causa: comillas, backslashes, o newlines en commit messages rompen el JSON.

Cómo manejar: usar slackapi/slack-github-action (que maneja escaping) o jq para construir el JSON.

4. Notificación llega pero se ve fea

Síntoma: el mensaje aparece como JSON crudo o sin formato.

Causa: usaste el formato simple {"text": "..."} en lugar de bloques.

Cómo manejar: usar Block Kit Builder de Slack (app.slack.com/block-kit-builder) para diseñar visualmente y generar el JSON.

5. No notificar a Slack cuando Slack tiene outage

Síntoma: tu workflow falla porque Slack API está caída.

Causa: el step de notificación retorna error y el job falla aunque el deploy fue exitoso.

Cómo manejar: agrega continue-on-error: true a los steps de notificación:

- name: Notify Slack
  continue-on-error: true   # falla del webhook no rompe el workflow
  if: success()
  run: ...

El deploy es lo importante; la notificación es opcional.


Caso desarrollado: el deploy fallido detectado por el equipo

Sin notificaciones:

14:30 — Bob mergea PR #43 a main
14:32 — CI corre, deploy job falla en health check
14:32 — GitHub Actions muestra ❌
14:35 — Bob se va a almorzar (no ve el error)
15:00 — Usuarios reportan errores en producción
15:15 — Alice investiga, encuentra que el deploy falló
15:30 — Alice intenta rollback, pero el código viejo y nuevo están mezclados
16:00 — Resuelven, pero 1.5 horas de prod degradada

Con notificaciones:

14:30 — Bob mergea PR #43 a main
14:32 — CI corre, deploy job falla en health check
14:32 — Slack notification: "🚨 Deploy FAILED - Bob's commit abc1234"
14:33 — Alice ve la notificación, click en "View Failed Run"
14:35 — Alice identifica: missing env var STRIPE_KEY
14:36 — Alice configura la env var en Railway
14:38 — Alice triggea redeploy manual
14:40 — ✅ Slack: "🚀 Deploy Successful"

10 minutos vs 90 minutos. Diferencia entre "issue contenida" vs "incident".


Ejercicio: integra notificaciones en tu pipeline

  1. Elige Slack o Discord según el stack de tu equipo.
  2. Crear webhook según las instrucciones de Step 1.
  3. Agregar SLACK_WEBHOOK_URL o DISCORD_WEBHOOK_URL a GitHub Secrets.
  4. Agregar steps de notificación success y failure al job deploy.
  5. Push de prueba (un commit trivial) → verificar que llega notificación de success.
  6. Romper intencionalmente algo (e.g., cambiar DATABASE_URL a algo inválido) → verificar que llega notificación de failure.
  7. Revertir el cambio → verificar notificación success de nuevo.
Solución: workflow con notifications completas (Slack)
deploy:
  name: Deploy
  runs-on: ubuntu-latest
  needs: build-and-push
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'

  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with: { python-version: '3.12' }
    - run: pip install -e ".[dev]"

    - name: Migrations
      env: { DATABASE_URL: '${{ secrets.DATABASE_URL }}' }
      run: alembic upgrade head

    - name: Redeploy
      env: { RAILWAY_TOKEN: '${{ secrets.RAILWAY_TOKEN }}' }
      run: |
        npm install -g @railway/cli
        railway redeploy --service ${{ secrets.RAILWAY_SERVICE_ID }}

    - name: Health check
      run: |
        DEPLOY_URL="${{ vars.DEPLOY_URL }}"
        for i in $(seq 1 24); do
          CODE=$(curl -s -o /dev/null -w "%{http_code}" "$DEPLOY_URL/health" || echo "000")
          [ "$CODE" = "200" ] && exit 0
          sleep 5
        done
        exit 1

    - name: Notify success
      if: success()
      continue-on-error: true
      uses: slackapi/slack-github-action@v1
      with:
        payload: |
          {
            "blocks": [
              {"type": "header", "text": {"type": "plain_text", "text": "🚀 Deploy Successful"}},
              {"type": "section", "fields": [
                {"type": "mrkdwn", "text": "*Repo:* ${{ github.repository }}"},
                {"type": "mrkdwn", "text": "*Author:* ${{ github.actor }}"},
                {"type": "mrkdwn", "text": "*Commit:* <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>"},
                {"type": "mrkdwn", "text": "*Branch:* ${{ github.ref_name }}"}
              ]},
              {"type": "actions", "elements": [
                {"type": "button", "text": {"type": "plain_text", "text": "View App"}, "url": "${{ vars.DEPLOY_URL }}"},
                {"type": "button", "text": {"type": "plain_text", "text": "View Run"}, "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}
              ]}
            ]
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

    - name: Notify failure
      if: failure()
      continue-on-error: true
      uses: slackapi/slack-github-action@v1
      with:
        payload: |
          {
            "blocks": [
              {"type": "header", "text": {"type": "plain_text", "text": "🚨 Deploy FAILED"}},
              {"type": "section", "text": {"type": "mrkdwn", "text": ":warning: *Author:* ${{ github.actor }}\n*Commit:* <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>\n*Investigate immediately*"}},
              {"type": "actions", "elements": [
                {"type": "button", "style": "danger", "text": {"type": "plain_text", "text": "View Failed Run"}, "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}
              ]}
            ]
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

Auto-verificación

1. ¿Por qué continue-on-error: true en los steps de notificación?

Razón: separar la prioridad del deploy de la prioridad de la notificación.

Sin continue-on-error: true:

1. Migrations ✅
2. Deploy ✅
3. Health check ✅
4. Notify Slack ❌ (Slack tiene outage)
5. Job marked: FAILED

Resultado: tu deploy fue exitoso, pero el job aparece como failed.

Con continue-on-error: true:

1. Migrations ✅
2. Deploy ✅
3. Health check ✅
4. Notify Slack ⚠️ (Slack outage, pero continúa)
5. Job marked: SUCCESS (con warning en el step de notify)

Resultado: deploy real exitoso, status correcto, solo pierdes la notificación.

La notificación es comunicación, no parte del deploy. Si Slack está caído, perdiste un canal de comunicación — pero tu producción funciona. El estado del job debe reflejar el deploy real, no la salud de Slack.

2. Tu canal #deploys tiene 200 mensajes por día. El equipo lo ignora. ¿Cómo lo arreglas?

Diagnóstico: hay demasiado ruido. Pasos para reducir:

1. Identificar fuentes de ruido. Mira los últimos 50 mensajes del canal y categoriza:

  • ¿Cuántos son deploys reales? (esperas 1-5/día en equipo activo)
  • ¿Cuántos son CI failures de PRs? (debería ser 0 acá)
  • ¿Cuántos son notificaciones de otros bots? (renovate, dependabot)

2. Separar canales por severidad:

  • #deploys-prod: solo deploys a producción success + failure. Pocos mensajes (1-10/día).
  • #deploys-staging: deploys a staging. Más mensajes pero menos críticos.
  • #alerts: solo failures que requieren acción inmediata.

Cada canal tiene su webhook. El workflow notifica al correcto según contexto.

3. Silenciar lo no-actionable:

  • Renovate/Dependabot a un canal aparte (#dependencies)
  • CI failures de feature branches a un canal aparte (#ci-feature-branches) o a ningún canal
  • Solo notifica en #deploys-prod lo que el equipo necesita actuar sobre

4. Threading por deploy:

Slack soporta threads. La primera notificación es el mensaje top-level; updates (build started, migration done, deploy done) van en thread. El canal ve 1 mensaje por deploy, no 5.

5. Filtros del cliente:

Slack permite que cada usuario configure notificaciones por keyword. Si el equipo ya no presta atención al canal, puedes sugerir que cada uno configure "notify me when 'FAILED' is mentioned in #deploys-prod" — así reciben notificaciones solo de failures.

Goal: que el canal de deploys tenga señal alta — cuando aparece algo, el equipo presta atención.


Resumen y siguiente paso

  • Notificaciones automáticas eliminan el "¿alguien deployó?" del equipo
  • Slack y Discord webhooks son la forma estándar — configuración en 5 min
  • Notifica success Y failure (failure es más crítico)
  • Incluye contexto útil: commit SHA con link, author, branch, mensaje
  • Usar if: success() y if: failure() para mensajes distintos
  • continue-on-error: true para que outage de Slack no rompa tu deploy
  • Separar canales por severidad evita ruido — #deploys#alerts

Puente al próximo paso: Tu pipeline ahora hace todo: CI gate, build, migrations, deploy, health check, notificación. Falta consolidarlo en el proyecto integrador del módulo. En la cápsula 08 vas a unir todas las piezas en el Auto-Deploy Pipeline: el pipeline completo end-to-end, con experiencia del ciclo PR → CI → merge → CD → app live → notification. Es la pieza que va a tu portfolio demostrando dominio de CI/CD profesional.


Recursos

  1. Slack Block Kit Builder — diseña mensajes visualmente.
  2. Incoming Webhooks for Slack — docs oficiales.
  3. Discord Webhook docs — referencia.
  4. slackapi/slack-github-action — action oficial de Slack para GHA.
  5. GitHub Actions context — variables disponibles (github.actor, github.sha, etc.).

Cápsula 07 de 08 — Módulo 3 — CI/CD for Python Backend Guide