{"record":{"id":"3cd092e02a037e58","repo":"postalserver/postal","slug":"error-when-scanning-with-rspamd-e-class","errorCode":null,"errorMessage":"Error when scanning with rspamd (#{e.class})","messagePattern":"Error when scanning with rspamd \\(#(.+?)\\)","errorType":"exception","errorClass":"Postal::MessageInspectors::Rspamd::Error","httpStatus":null,"severity":"warning","filePath":"lib/postal/message_inspectors/rspamd.rb","lineNumber":63,"sourceCode":"        request[\"Queue-Id\"] = message.token\n\n        if scope == :outgoing\n          request[\"User\"] = \"\"\n          # We don't actually know the IP but an empty input here will\n          # still trigger rspamd to treat this as an outbound email\n          # and disable certain checks.\n          # https://rspamd.com/doc/tutorials/scanning_outbound.html\n          request[\"Ip\"] = \"\"\n        end\n\n        response = nil\n        begin\n          response = http.request(request)\n        rescue StandardError => e\n          logger.error \"Error talking to rspamd: #{e.class} (#{e.message})\"\n          logger.error e.backtrace[0, 5]\n\n          raise Error, \"Error when scanning with rspamd (#{e.class})\"\n        end\n\n        unless response.is_a?(Net::HTTPOK)\n          logger.info \"Got #{response.code} status from rspamd, wanted 200\"\n          raise Error, \"Error when scanning with rspamd (got #{response.code})\"\n        end\n\n        response\n      end\n\n    end\n  end\nend\n","sourceCodeStart":45,"sourceCodeEnd":77,"githubUrl":"https://github.com/postalserver/postal/blob/d038eaa8c763d3cafa797ccd6f773d53470bd336/lib/postal/message_inspectors/rspamd.rb#L45-L77","documentation":"Postal's rspamd message inspector POSTs messages to rspamd's /checkv2 with 10s open/read timeouts; any StandardError from that request (ECONNREFUSED, timeout, DNS failure, TLS error) is first logged ('Error talking to rspamd: ...') and then re-raised as Rspamd::Error with the original class name interpolated. inspect_message catches Error and records a SpamCheck named ERROR, so the message still flows but spam inspection is degraded for it.","triggerScenarios":"Delivering/scanning a message while rspamd is stopped or unreachable at the configured host/port; wrong host, port, or ssl setting in the message_inspectors.rspamd config; rspamd too slow so the 10 second read/open timeout trips; TLS handshake failure when ssl is enabled but the port is plain HTTP.","commonSituations":"rspamd container/service not started after deploy or host reboot; config pointing at 127.0.0.1 while rspamd runs in a different container/network; port mismatch (default controller port vs worker port); rspamd overloaded and slow; firewall between Postal and rspamd.","solutions":["Confirm rspamd is running and reachable from the Postal host: curl -v http://<host>:<port>/ping","Correct host/port/ssl in the message_inspectors.rspamd section of postal.yml and restart Postal","If timeouts are the cause (slow rspamd under load), give rspamd more resources or check its logs for stalls","Confirm network paths (docker network, firewall) allow Postal to reach the rspamd port","Watch the Postal log for the preceding 'Error talking to rspamd: <Class> (<message>)' line - it names the real network failure"],"exampleFix":"# before (postal.yml)\nmessage_inspectors:\n  rspamd:\n    host: 127.0.0.1\n    port: 11333\n# ECONNREFUSED because rspamd runs in another container\n\n# after\nmessage_inspectors:\n  rspamd:\n    host: rspamd\n    port: 11334\n    ssl: true\n# matches the rspamd container's service name and TLS controller port","handlingStrategy":"retry","validationCode":"# pre-flight reachability check for the rspamd controller\nrequire \"socket\"\nrequire \"openssl\"\nsock = TCPSocket.new(config.host, config.port)\nbegin\n  sock = OpenSSL::SSL::SSLSocket.new(sock).connect if config.ssl\nensure\n  sock.close\nend","typeGuard":"def rspamd_reachable?(config)\n  TCPSocket.new(config.host, config.port).close\n  true\nrescue StandardError\n  false\nend","tryCatchPattern":"# inside a custom inspector wrapper around Postal's Rspamd inspector\nbegin\n  inspector.inspect_message(inspection)\nrescue Postal::MessageInspectors::Rspamd::Error => e\n  # transient transport failure: retry a bounded number of times with backoff,\n  # then record the degraded result (Postal itself already adds an ERROR spam check)\n  retry if (attempts += 1) < 3\nend","preventionTips":["Health-check the rspamd host/port from the Postal container (it already exposes /ping) and alert on failure","Pin host/port/ssl config to the rspamd controller and review it after every rspamd upgrade","Size rspamd for peak scan load so the 10s open/read timeouts are not hit","Keep the 'Error talking to rspamd: <Class> (<message>)' log line monitored - it carries the real network cause"],"tags":["postal","rspamd","spam","network","timeouts"],"backgroundTag":"upstream-service-unreachable","analyzedSha":"d038eaa8c763d3cafa797ccd6f773d53470bd336","analyzedAt":"2026-08-21T13:52:57.446Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}