[
  {
    "day": "D-7",
    "action": "Generate the pair and publish the inactive selector in DNS",
    "command": "openssl genrsa -out dkim_private.pem 2048",
    "verification": "dig +short TXT s2026-q1._domainkey.captaindns.com"
  },
  {
    "day": "D-7",
    "action": "Store the private key in KMS or Vault",
    "command": "vault kv put secret/dkim/captaindns/s2026-q1 private_key=@dkim_private.pem",
    "verification": "vault kv get secret/dkim/captaindns/s2026-q1"
  },
  {
    "day": "D+0",
    "action": "Switch MTA signing to the new selector",
    "command": "aws sesv2 put-email-identity-dkim-signing-attributes --email-identity captaindns.com",
    "verification": "Check the DKIM-Signature header on outbound messages"
  },
  {
    "day": "D+1",
    "action": "Monitor DMARC aggregate reports (rua) for 24h",
    "command": "N/A (rua parsing)",
    "verification": "auth_results/dkim/selector = s2026-q1 for 100% of traffic"
  },
  {
    "day": "D+7",
    "action": "Confirm 100% of traffic signed by the new selector",
    "command": "N/A (7-day rua aggregation)",
    "verification": "Target threshold: under 1% on the old selector"
  },
  {
    "day": "D+8",
    "action": "Remove MTA signing for the old selector (TXT remains published)",
    "command": "Edit OpenDKIM KeyTable and SigningTable then reload",
    "verification": "No outbound message signed with the old selector"
  },
  {
    "day": "D+30",
    "action": "Delete the TXT of the old selector and destroy the private key in KMS",
    "command": "curl -X DELETE Cloudflare API dns_records then vault kv destroy",
    "verification": "dig +short TXT s2025-q4._domainkey.captaindns.com returns empty"
  }
]
