{"record":{"id":"08ee803300df8cdf","repo":"postalserver/postal","slug":"could-not-resolve-host-to-any-ip-address","errorCode":null,"errorMessage":"Could not resolve '#{@host}' to any IP address","messagePattern":"Could not resolve '#(.+?)' to any IP address","errorType":"exception","errorClass":"Postal::HTTP::BlockedDestinationError","httpStatus":null,"severity":"error","filePath":"lib/postal/http/address_guard.rb","lineNumber":102,"sourceCode":"          end\n          families.uniq\n        end\n\n      end\n\n      # @param [String] host\n      def initialize(host)\n        @host = host.to_s\n      end\n\n      def safe_connect_address\n        if @host.empty?\n          raise BlockedDestinationError, \"No host was given for the request\"\n        end\n\n        addresses = resolve\n        if addresses.empty?\n          raise BlockedDestinationError, \"Could not resolve '#{@host}' to any IP address\"\n        end\n\n        # Reject the whole request if *any* resolved address is blocked. This is\n        # checked before the reachability filtering below so that a blocked\n        # destination is always reported as such, regardless of which address\n        # families this particular server can reach. It also defeats DNS\n        # responses that mix a public and a private address to slip past.\n        addresses.each do |address|\n          next unless blocked?(address)\n\n          raise BlockedDestinationError,\n                \"Destination '#{@host}' (#{address}) is not permitted\"\n        end\n\n        # Only connect to an address whose family this server can actually\n        # reach. Otherwise we might pin the connection to an IPv6 address on a\n        # host without IPv6 connectivity and fail to connect even when a usable\n        # IPv4 address was available.","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/postalserver/postal/blob/d038eaa8c763d3cafa797ccd6f773d53470bd336/lib/postal/http/address_guard.rb#L84-L120","documentation":"AddressGuard resolves the host of every outbound request (via Resolv) and refuses to continue when zero A/AAAA records come back. The error string interpolates the offending host, so the message reads e.g. Could not resolve 'hooks.exampl.com' to any IP address. It is raised as BlockedDestinationError, which Postal::HTTP maps to a failed request rather than connecting.","triggerScenarios":"An HTTPEndpoint/webhook URL whose hostname has no DNS records: typo'd domain (double letters, .con instead of .com), an expired/dropped domain, a freshly created DNS record not yet propagated, or the Postal host's resolver being broken (empty/misconfigured /etc/resolv.conf, unreachable DNS in the container).","commonSituations":"Typo in the webhook hostname; recently registered domain whose records have not propagated; internal-only DNS names that the Postal host's resolver does not know; containerized Postal with missing DNS config; temporary resolver outage at delivery time.","solutions":["Verify the hostname resolves from the Postal host: dig +short <host> or getent hosts <host>","Fix the typo/expired domain in the endpoint URL","If the record is new, wait for propagation and retry delivery","If DNS works elsewhere but not from Postal, fix the host's resolver (/etc/resolv.conf, docker --dns, cluster DNS)"],"exampleFix":"# before\nendpoint.url = \"https://hooks.exampl.com/receive\"\n\n# after\nendpoint.url = \"https://hooks.example.com/receive\"\n\n# sanity check from the Postal host:\n# dig +short hooks.example.com  # must return at least one A/AAAA record","handlingStrategy":"validation","validationCode":"# pre-flight DNS check before configuring or calling the endpoint\nrequire \"resolv\"\naddresses = Resolv::DNS.open { |dns| dns.getresources(host, Resolv::DNS::Resource::IN::A) + dns.getresources(host, Resolv::DNS::Resource::IN::AAAA) }\nraise ArgumentError, \"#{host} does not resolve\" if addresses.empty?","typeGuard":"def resolvable_host?(host)\n  require \"resolv\"\n  Resolv::DNS.open do |dns|\n    dns.getresources(host, Resolv::DNS::Resource::IN::A).any? ||\n      dns.getresources(host, Resolv::DNS::Resource::IN::AAAA).any?\n  end\nend","tryCatchPattern":"begin\n  Postal::HTTP.request(...)\nrescue Postal::HTTP::BlockedDestinationError => e\n  if e.message.include?(\"Could not resolve\")\n    # DNS problem: surface it (typo/propagation) rather than retrying immediately\n  end\nend","preventionTips":["Run a resolve check (dig/getent) when an endpoint URL is first saved","Prefer stable corporate domains over ad-hoc hostnames for webhooks","Monitor Postal's DNS health in containers (resolv.conf present, resolver reachable)","Remember new DNS records can take minutes-to-hours to propagate before first use"],"tags":["postal","dns","http-endpoint","webhook","network"],"backgroundTag":"dns-resolution-failed","analyzedSha":"d038eaa8c763d3cafa797ccd6f773d53470bd336","analyzedAt":"2026-08-21T13:52:57.446Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}