{"record":{"id":"a0cdc202e60c6aec","repo":"postalserver/postal","slug":"host-only-resolves-to-addresses-this-server","errorCode":null,"errorMessage":"'#{@host}' only resolves to addresses this server cannot reach (#{addresses.join(', ')})","messagePattern":"'#(.+?)' only resolves to addresses this server cannot reach \\(#(.+?)\\)","errorType":"exception","errorClass":"SocketError","httpStatus":null,"severity":"error","filePath":"lib/postal/http/address_guard.rb","lineNumber":123,"sourceCode":"        # 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.\n        usable = addresses.select { |address| family_reachable?(address) }\n        if usable.empty?\n          raise SocketError,\n                \"'#{@host}' only resolves to addresses this server cannot reach \" \\\n                \"(#{addresses.join(', ')})\"\n        end\n\n        # Prefer IPv4 for predictability; only use IPv6 when it is the only\n        # reachable option.\n        (usable.find(&:ipv4?) || usable.first).to_s\n      end\n\n      private\n\n      # @return [Array<IPAddr>]\n      def resolve\n        return [IPAddr.new(@host)] if ip_literal?\n\n        Resolv.getaddresses(@host).filter_map do |address|\n          IPAddr.new(address)\n        rescue IPAddr::InvalidAddressError","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/postalserver/postal/blob/d038eaa8c763d3cafa797ccd6f773d53470bd336/lib/postal/http/address_guard.rb#L105-L141","documentation":"After the blocked-range check passes, AddressGuard filters resolved addresses to families this server can actually reach; if none remain it raises SocketError with the full address list. In practice this means the host resolved only to AAAA (IPv6) records while the Postal server has no IPv6 connectivity, so the request would be pinned to an unusable address.","triggerScenarios":"An HTTPEndpoint/webhook hostname that is IPv6-only (AAAA records, no A record) called from a Postal host without working IPv6; or a host whose A record exists but whose family is unreachable in this environment (unusual routing setups, IPv6 disabled in the container).","commonSituations":"Modern domains that publish AAAA only; Docker/container deployments with IPv6 disabled while the target moved to v6-only; the endpoint provider deprecating its A record; cloud nodes without an assigned IPv6 address.","solutions":["Ask the endpoint host to publish an IPv4 A record and use that hostname","Use an alternative hostname for the same service that still has an A record","Enable working IPv6 on the Postal host (address, route, and DNS AAAA support in the container) so family_reachable? succeeds","Put a dual-stack proxy in front of the v6-only service and point the endpoint at the proxy"],"exampleFix":"# before\nendpoint.url = \"https://v6-only.example.com/hook\"  # resolves to AAAA only -> SocketError\n\n# after\nendpoint.url = \"https://dual-stack.example.com/hook\"  # has an A record\n\n# verify from the Postal host:\n# dig A v6-only.example.com +short   # empty\ndig A dual-stack.example.com +short  # returns an IPv4 address","handlingStrategy":"validation","validationCode":"# ensure the host has at least one IPv4 address before using it\nipv4 = Resolv::DNS.open { |dns| dns.getresources(host, Resolv::DNS::Resource::IN::A).map(&:address) }\nraise ArgumentError, \"#{host} has no IPv4 address and this server lacks IPv6\" if ipv4.empty? && !Addrinfo.ip(\"::1\").ipv6? && !ipv6_connectivity?","typeGuard":"def reachable_address_family?(host)\n  require \"resolv\"\n  addrs = Resolv.getaddresses(host)\n  has_v4 = addrs.any? { |a| IPAddr.new(a).ipv4? }\n  # if only v6, verify this host actually has IPv6 connectivity\n  has_v4 || (addrs.any? && system(\"ping6 -c1 -W1 ::1 > /dev/null 2>&1\") && ipv6_route_present?)\nend","tryCatchPattern":"begin\n  Postal::HTTP.request(...)\nrescue SocketError => e\n  # address-family problem: switch endpoint to a hostname with an A record\n  # or enable IPv6 on this server; retrying unchanged will keep failing\nend","preventionTips":["Prefer endpoint hostnames that publish both A and AAAA records","Verify IPv6 connectivity of the Postal host (ip -6 route) before pointing it at v6-only services","In Docker, enable IPv6 networking explicitly if v6-only targets are expected","When a provider announces v6-only deprecation, update endpoint URLs before the A record disappears"],"tags":["postal","ipv6","dns","http-endpoint","network"],"backgroundTag":"ipv6-unreachable","analyzedSha":"d038eaa8c763d3cafa797ccd6f773d53470bd336","analyzedAt":"2026-08-21T13:52:57.446Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}