Ir para o conteúdo principal

Monitores HTTP, marca branca, domínio personalizado. No ar em 3 minutos.

Monitores e grupos

  • Monitores HTTP ilimitados
  • Agrupamento por serviço ou região

100% personalizável

  • Logo e paleta de cores
  • Título e meta SEO
  • Conteúdo livre
Novo Nova funcionalidade

Marca branca

  • Sem menção ao CaptainDNS
  • Seu domínio via CNAME
  • TLS automático

Tempo real e histórico

  • Sincronizado com seus monitores
  • Histórico de 30 dias
  • Incidentes e manutenções

Validar DKIM no seu CI/CD: detectar registros quebrados antes da produção

Por CaptainDNS
Publicado em 19 de maio de 2026

Pipeline CI/CD com validação DKIM: hooks pre-commit, GitHub Actions, GitLab CI, Terraform e smoke test pós-deploy
TL;DR
  • Um registro DKIM quebrado raramente falha nos testes funcionais mas quebra a deliverability já no primeiro e-mail pós-deploy
  • Os linters locais detectam a sintaxe; a API CaptainDNS /v1/dkim/validate verifica o estado real publicado no DNS
  • GitHub Actions, GitLab CI e hooks do Terraform podem bloquear um merge ou um apply em um score DKIM insuficiente
  • A validação pre-commit economiza minutos de CI mas não substitui uma verificação do estado DNS publicado
  • Combinar as validações SPF, DKIM e DMARC em um único stage evita regressões em cascata

Você versiona o seu DNS no Git, as suas rotações de chaves DKIM passam por uma pull request, e tudo vai bem até o dia em que o Gmail começa a marcar os seus e-mails como spam. A causa: um tag p= truncado por um provider DNS, ou uma assinatura que não corresponde mais ao seletor publicado. O bug não aparece em terraform plan, nem nos testes de integração da aplicação.

A resposta cabe em uma palavra: shift-left. Em vez de descobrir o problema em produção, valide cada modificação DKIM antes do merge, depois antes do apply, depois após o deploy. Este artigo mostra como integrar esses controles no GitHub Actions, GitLab CI, Terraform e em um hook pre-commit, com exemplos copy-paste.

Para verificar rapidamente a sintaxe de um registro, o DKIM Syntax Check fornece uma API pública reutilizável em qualquer pipeline.

Por que um registro DKIM quebrado chega à produção

Quatro causas principais explicam por que um DKIM inválido atravessa as defesas padrão de um deployment DNS.

Edição manual dos registros DNS. Um operador cola uma chave pública de um provider transacional, esquece um caractere, ou troca dois blocos base64. Sem validação sintática, o registro é publicado tal como está.

Rotação de chave sem validação. O novo par é gerado, a chave pública é codificada, mas o pipeline de rotação não revalida que a string base64 permaneça coerente após a exportação. Um truncamento a 254 caracteres em alguns painéis de administração basta para quebrar a assinatura.

Provider DNS que faz split em dois chunks. Uma string TXT com mais de 255 caracteres deve ser fragmentada pelo servidor DNS, que a concatena com aspas. Alguns providers (ou algumas UIs) cortam a string sem aspas: o resolver recebe então duas strings distintas, e a chave reconstituída é inválida.

Sem canary pós-deploy. O bug só aparece nos primeiros e-mails enviados após a mudança. Quando a informação chega (relatório DMARC, ticket de suporte), milhares de mensagens já foram classificadas como spam.

Shift-left: validar antes do merge

O princípio é simples: qualquer modificação de um registro DNS ligado ao e-mail passa por uma validação automática em pull request. Você tem duas opções para validar.

Linter local open source. Ferramentas como dkim-checker (CLI Go baseada em github.com/emersion/go-msgauth/dkim) ou scripts caseiros verificam a sintaxe de um registro, a coerência das tags (v=DKIM1, k=rsa, p=...) e o tamanho da chave. Vantagem: zero dependência de rede, rápido. Desvantagem: não sabe se a chave está realmente publicada e acessível.

Linter cloud via API. O endpoint CaptainDNS /v1/dkim/validate aceita um domínio e um seletor, consulta o DNS publicado, parseia o registro e retorna um score normalizado, um estado (valid, invalid, warning) e recomendações. Vantagem: valida o estado real observado do exterior, como um MX que recebesse o e-mail. Desvantagem: depende da rede, do limite da API e do DNS.

CritérioLinter localAPI CaptainDNS
Sintaxe (v=, k=, p=)SimSim
Chave publicada no DNSNãoSim
Tamanho efetivo da chaveParcialSim
Score normalizadoNãoSim (calculado no backend)
Dependência de redeNãoSim
Caso de usopre-commit, lint sintaxevalidação PR, pós-deploy

Nota importante: o score, os limites e as recomendações são calculados pela API CaptainDNS no backend. O cliente CI consome um valor já normalizado, não refaz nenhum cálculo.

Combine os dois: linter local em pre-commit para iterações rápidas, API em CI para a validação final antes do merge.

Pipeline GitHub Actions para validar uma pull request DNS

Aqui está um workflow que é acionado a cada pull request que modifica arquivos DNS. Ele extrai os registros DKIM, chama a API CaptainDNS e bloqueia o merge se o estado for invalid ou se o score for inferior a 50.

name: Validate DKIM records

on:
  pull_request:
    paths:
      - 'dns/**.tf'
      - 'dns/**.yaml'

jobs:
  validate-dkim:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Detect changed DNS files
        id: changes
        uses: dorny/paths-filter@v3
        with:
          filters: |
            dns:
              - 'dns/**'

      - name: Validate DKIM via CaptainDNS API
        if: steps.changes.outputs.dns == 'true'
        env:
          CDNS_API_KEY: ${{ secrets.CAPTAINDNS_API_KEY }}
        run: |
          set -euo pipefail
          for entry in $(yq '.dkim_selectors[]' dns/email.yaml); do
            domain=$(echo "$entry" | yq '.domain')
            selector=$(echo "$entry" | yq '.selector')

            response=$(curl -sS -X POST \
              -H "Authorization: Bearer ${CDNS_API_KEY}" \
              -H "Content-Type: application/json" \
              -d "{\"domain\":\"${domain}\",\"selector\":\"${selector}\"}" \
              https://api.captaindns.com/public/v1/dkim/validate)

            state=$(echo "$response" | jq -r '.state')
            score=$(echo "$response" | jq -r '.score')

            echo "Selector ${selector} for ${domain}: state=${state}, score=${score}"

            if [ "$state" = "invalid" ] || [ "$score" -lt 50 ]; then
              echo "::error::DKIM validation failed for ${selector}._domainkey.${domain}"
              exit 1
            fi
          done

A chave API é provisionada a partir do console CaptainDNS e depois armazenada como secret do GitHub (Settings > Secrets and variables > Actions). Use uma chave com escopo dkim:read para limitar o raio de ação em caso de vazamento.

Pipeline GitHub Actions validando um registro DKIM via a API CaptainDNS

GitLab CI: pipeline com stage validate

No GitLab a lógica permanece idêntica. O arquivo .gitlab-ci.yml abaixo adiciona um cache para evitar revalidar registros inalterados e envia uma notificação para o Slack em caso de falha.

stages:
  - validate
  - apply

validate-dkim:
  stage: validate
  image: alpine:3.20
  before_script:
    - apk add --no-cache curl jq yq bash
  cache:
    key: dkim-validation-$CI_COMMIT_REF_SLUG
    paths:
      - .dkim-cache/
  script:
    - mkdir -p .dkim-cache
    - |
      bash -c '
      set -euo pipefail
      for entry in $(yq ".dkim_selectors[]" dns/email.yaml); do
        domain=$(echo "$entry" | yq ".domain")
        selector=$(echo "$entry" | yq ".selector")
        cache_key=".dkim-cache/${domain}_${selector}.json"

        response=$(curl -sS -X POST \
          -H "Authorization: Bearer ${CAPTAINDNS_API_KEY}" \
          -H "Content-Type: application/json" \
          -d "{\"domain\":\"${domain}\",\"selector\":\"${selector}\"}" \
          https://api.captaindns.com/public/v1/dkim/validate)

        echo "$response" > "$cache_key"
        state=$(echo "$response" | jq -r ".state")
        score=$(echo "$response" | jq -r ".score")

        if [ "$state" = "invalid" ] || [ "$score" -lt 50 ]; then
          curl -X POST -H "Content-Type: application/json" \
            -d "{\"text\":\"DKIM validation failed: ${selector}._domainkey.${domain} (score=${score})\"}" \
            "$SLACK_WEBHOOK_URL"
          exit 1
        fi
      done
      '
  only:
    changes:
      - dns/**/*

O cache do GitLab guarda as respostas entre dois runs no mesmo branch. Se um seletor não mudou, a API ainda é chamada (o estado DNS pode evoluir independentemente do código), mas o histórico permite rastrear regressões.

Terraform pre-apply: validação antes da publicação DNS

Quando o seu DNS é gerenciado por Terraform, Pulumi ou OpenTofu, a validação lógica acontece antes do apply. O hook abaixo extrai os TXT modificados do plano JSON, valida-os via a API CaptainDNS e bloqueia o apply em caso de falha.

#!/usr/bin/env bash
# scripts/dkim-preapply.sh
set -euo pipefail

terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

# Extrair os registros TXT DKIM modificados
jq -r '
  .resource_changes[]
  | select(.type == "cloudflare_record" or .type == "aws_route53_record")
  | select(.change.after.type == "TXT")
  | select(.change.after.name | test("_domainkey"))
  | "\(.change.after.name)|\(.change.after.value // (.change.after.records | join("")))"
' tfplan.json > dkim-changes.txt

while IFS='|' read -r fqdn value; do
  # fqdn: selector._domainkey.captaindns.com
  selector="${fqdn%%._domainkey.*}"
  domain="${fqdn#*._domainkey.}"

  response=$(curl -sS -X POST \
    -H "Authorization: Bearer ${CDNS_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "{\"domain\":\"${domain}\",\"selector\":\"${selector}\",\"record\":\"${value}\"}" \
    https://api.captaindns.com/public/v1/dkim/validate)

  state=$(echo "$response" | jq -r '.state')
  if [ "$state" = "invalid" ]; then
    echo "Pre-apply DKIM check failed: ${fqdn}"
    echo "$response" | jq '.recommendations'
    exit 1
  fi
done < dkim-changes.txt

terraform apply tfplan.binary

Conecte esse script em um wrapper make plan && make validate && make apply, ou no stage Atlantis se você usa esse padrão GitOps. A mesma lógica se aplica ao Pulumi (via pulumi preview --json) e ao OpenTofu, dado que o formato JSON do plano é compatível.

Lint local em pre-commit

Para economizar minutos de CI, adicione um hook pre-commit que valida a sintaxe dos registros DKIM antes mesmo do push. O framework pre-commit.com permite orquestrar vários hooks Python ou bash.

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: dkim-syntax-lint
        name: DKIM syntax lint
        entry: scripts/dkim-lint.sh
        language: script
        files: ^dns/.*\.(tf|yaml)$
        pass_filenames: true

O script dkim-lint.sh pode se apoiar em um parser local Go ou Python que verifica a presença das tags obrigatórias (v=DKIM1, p=...) e a validade base64 da chave pública. Basta para pegar 80 % dos erros de copiar e colar.

Limite a conhecer: um lint pre-commit não consegue verificar o estado real publicado no DNS, nem conflitos com um seletor já ativo. A validação API em CI continua indispensável para esses casos.

Testes de integração pós-deploy

Uma vez o DNS aplicado, verifique se a cadeia fim a fim funciona. Um smoke test envia um e-mail real e verifica o header Authentication-Results.

#!/usr/bin/env bash
# scripts/dkim-smoke-test.sh
set -euo pipefail

# Enviar um e-mail de teste via swaks
swaks --to test-canary@captaindns.com \
      --from noreply@captaindns.com \
      --server smtp.captaindns.com \
      --header "Subject: DKIM canary $(date +%s)" \
      --body "Canary message"

# Aguardar 60 segundos para o e-mail chegar
sleep 60

# Verificar o header Authentication-Results via IMAP ou um endpoint próprio
result=$(curl -sS "https://canary.captaindns.com/last-mail/auth-results")

if ! echo "$result" | grep -q "dkim=pass"; then
  echo "Canary DKIM check FAILED: $result"
  # Rollback automático
  terraform apply -auto-approve -var "dkim_selector=previous"
  exit 1
fi

Você também pode usar serviços sintéticos como Mail-Tester ou GlockApps, que expõem uma API para recuperar os resultados de autenticação de um e-mail enviado a um endereço descartável. O ciclo de feedback torna-se: envio do e-mail, leitura do resultado, depois rollback do DNS se o DKIM falhar.

Para uma verificação pontual sem pipeline, o DKIM Record Check consulta o DNS e mostra o estado completo do seletor em poucos segundos.

Combinar com SPF / DMARC

Validar DKIM sozinho não basta. Um DKIM valid mas um SPF quebrado gera um dmarc=fail nos e-mails não assinados pelo DKIM. Um DMARC mal alinhado degrada a deliverability mesmo com um DKIM perfeito.

A pipeline completa deve, portanto, validar os três protocolos em um mesmo stage:

validate-email-auth:
  stage: validate
  script:
    - bash scripts/validate-spf.sh dns/email.yaml
    - bash scripts/validate-dkim.sh dns/email.yaml
    - bash scripts/validate-dmarc.sh dns/email.yaml
    - bash scripts/validate-bimi.sh dns/email.yaml  # opcional

Cada script chama o endpoint correspondente: /v1/spf/validate, /v1/dkim/validate, /v1/dmarc/validate. Compartilhar o cliente HTTP, o parsing da resposta e os limites em um módulo comum evita a duplicação.

Um único stage CI validando SPF, DKIM, DMARC e BIMI em paralelo via a API CaptainDNS

Plano de ação recomendado

  1. Inventário: liste seus seletores DKIM ativos por domínio e subdomínio
  2. Provisionar uma chave API CaptainDNS com escopo dkim:read + spf:read + dmarc:read
  3. Adicionar o hook pre-commit para o lint de sintaxe local
  4. Montar um workflow CI que valida os arquivos DNS modificados em PR
  5. Conectar o Terraform a um hook pre-apply que bloqueia os registros inválidos
  6. Ativar um canary pós-deploy: um e-mail de teste enviado após cada apply
  7. Estender aos outros protocolos: SPF, DMARC, BIMI no mesmo stage de validação

FAQ

Por que validar DKIM no CI/CD?

Um registro DKIM quebrado não gera erro visível no lado DNS. Ele só quebra a deliverability dos e-mails, o que se constata com algumas horas de atraso via os relatórios DMARC ou os retornos dos usuários. Validar em CI permite bloquear o merge antes que milhares de e-mails sejam enviados com uma assinatura inválida.

Qual a diferença entre validação local e validação via API?

Um linter local verifica apenas a sintaxe de um registro (tags, formato base64). A API CaptainDNS consulta o DNS publicado, parseia o registro como um receptor o veria e retorna um score calculado no backend. O local é rápido mas cego ao estado real publicado.

Como integrar a validação DKIM no GitHub Actions?

Crie um workflow acionado nas pull requests que modificam a pasta DNS. Recupere os pares domínio/seletor, chame o endpoint /v1/dkim/validate via curl, parseie a resposta com jq, e faça o job falhar se o estado for invalid ou se o score for inferior ao seu limite (50 é um valor de partida razoável).

Dá para bloquear um terraform apply em um DKIM inválido?

Sim. Execute terraform plan -out=tfplan e depois terraform show -json para extrair os registros TXT modificados. Valide cada registro via a API CaptainDNS antes de lançar terraform apply. Se a validação falhar, saia do script com um código diferente de zero para bloquear o apply.

Também é preciso validar SPF e DMARC na mesma pipeline?

Sim. DKIM, SPF e DMARC são interdependentes. Um DKIM válido isolado não garante a deliverability se o SPF falha ou se o DMARC não está alinhado. Valide os três no mesmo stage CI, com fail-fast no primeiro erro ou um relatório consolidado.

Quais secrets configurar para a API CaptainDNS no CI?

Um único secret basta: CAPTAINDNS_API_KEY. Provisione-o a partir do console, dê escopo aos endpoints necessários (leitura DKIM/SPF/DMARC) e armazene-o no secret manager do seu CI. Evite chaves admin em uma pipeline CI, que precisa apenas de leitura.

O que fazer se o registro passa localmente mas falha após o deploy?

Três causas frequentes: propagação DNS incompleta (aguarde 30 minutos), provider DNS que fez split do TXT sem aspas (verifique com dig +short TXT seletor._domainkey.dominio), ou cache negativo em um resolver intermediário. O smoke test pós-deploy deve tentar novamente com backoff exponencial antes de disparar um rollback.

Glossário

  • Shift-left: prática que consiste em deslocar os controles de qualidade para as fases iniciais do ciclo de desenvolvimento (PR, pre-commit), em vez de deixá-los na produção.
  • Pre-commit hook: script executado localmente antes de cada commit Git, que pode bloquear o commit em caso de erro.
  • Pre-apply hook: script executado antes de terraform apply (ou equivalente Pulumi/OpenTofu) que valida as mudanças planejadas.
  • Canary: teste pós-deployment com um volume reduzido que detecta as regressões antes que afetem todo o tráfego.
  • Score DKIM: valor normalizado (0 a 100) calculado pela API CaptainDNS a partir da sintaxe, do tamanho da chave, do algoritmo e das recomendações RFC.
  • ARC (Authenticated Received Chain): protocolo RFC 8617 que preserva os resultados de autenticação DKIM/SPF durante o encaminhamento de e-mails.

Teste sua integração com o DKIM Syntax Check: a ferramenta expõe a mesma API utilizada em CI/CD, ideal para validar um registro antes de adicioná-lo à sua pipeline.


Guias DKIM relacionados para continuar

Fontes

Artigos relacionados