sj26/mailcatcher · warning
Error sending message through websocket
Error message
Error sending message through websocket
What it means
While pushing a Bus event (new message, clear, remove, quit) to a subscribed browser over the /messages websocket, ws.send(JSON.generate(message)) raised (application.rb:88-91). The rescue logs the bus message as context and keeps the server running. In almost all cases the exception is a write to a socket the browser already closed, so the log line is noise rather than a fault: the on(:close) handler unsubscribes the connection, but messages pushed in the window before close-fire arrive at a dead socket.
Source
Thrown at lib/mail_catcher/web/application.rb:90
if MailCatcher.quittable?
MailCatcher.quit!
status 204
else
status 403
end
end
get "/messages" do
if request.websocket?
bus_subscription = nil
ws = Faye::WebSocket.new(request.env)
ws.on(:open) do |_|
bus_subscription = MailCatcher::Bus.subscribe do |message|
begin
ws.send(JSON.generate(message))
rescue => exception
MailCatcher.log_exception("Error sending message through websocket", message, exception)
end
end
end
ws.on(:close) do |_|
MailCatcher::Bus.unsubscribe(bus_subscription) if bus_subscription
end
ws.rack_response
else
content_type :json
JSON.generate(Mail.messages)
end
end
delete "/messages" do
Mail.delete!
status 204View on GitHub (pinned to 18a9cb7a79)
Solutions
- Treat isolated occurrences as benign — the subscription is cleaned up on :close and other clients still receive events; verify the UI still updates before doing anything.
- Confirm the Exception line is a disconnect class (Errno::EPIPE, Errno::ECONNRESET, IOError); anything else (JSON::GeneratorError etc.) deserves investigation.
- If the log floods, upgrade the mailcatcher gem — newer websocket handling checks connection state before sending.
- Reduce trigger surface: close MailCatcher tabs you are not using, and avoid multiple stale tabs against one daemon.
Example fix
# before (application.rb:86-92)
bus_subscription = MailCatcher::Bus.subscribe do |message|
begin
ws.send(JSON.generate(message))
rescue => exception
MailCatcher.log_exception("Error sending message through websocket", message, exception)
end
end
# after: only write to sockets that are still open
bus_subscription = MailCatcher::Bus.subscribe do |message|
begin
ws.send(JSON.generate(message)) if ws.ready_state == Faye::WebSocket::OPEN
rescue => exception
MailCatcher.log_exception("Error sending message through websocket", message, exception)
end
end Defensive patterns
Strategy: validation
Validate before calling
# If you embed or fork the websocket handler: check state before writing
payload = JSON.generate(message)
ws.send(payload) if ws.ready_state == Faye::WebSocket::OPEN
# For plain users of the stock UI: verify the endpoint is live before relying on pushes
require "net/http"
Net::HTTP.new("127.0.0.1", 1080).head("/messages").code == "200" Type guard
# Guard the socket before every push in your own Bus subscriber def open_websocket?(ws) ws.respond_to?(:ready_state) && ws.ready_state == Faye::WebSocket::OPEN end bus.on_push do |message| ws.send(JSON.generate(message)) if open_websocket?(ws) end
Try / catch
# Keep the library's pattern: swallow disconnect-class errors, log the rest
begin
ws.send(JSON.generate(message))
rescue Errno::EPIPE, Errno::ECONNRESET, IOError
# client went away; on(:close) will unsubscribe — ignore
rescue => e
MailCatcher.log_exception("Error sending message through websocket", message, e)
end Prevention
- Close MailCatcher browser tabs when done; stale tabs are the main source of these reports.
- In browser automation, tear down WebSocket connections cleanly (call ws.close()) instead of killing the page.
- Keep the mailcatcher gem current so connection-state checks before send are picked up.
- Treat isolated occurrences as informational; alert only on floods or non-disconnect exception classes.
When it happens
Trigger: A browser tab viewing the MailCatcher UI is closed, refreshed, or sleeps at the same moment a new SMTP message arrives (Bus 'add' push); many browser tabs open and one disconnects; a client that opens the WS at /messages and drops without a clean close handshake; also possible, though rarer, a JSON.generate failure on an exotic message payload.
Common situations: Leaving MailCatcher's web UI open in a tab that gets closed right as tests send mail; CI screenshots/browser automation hitting /messages; laptop sleep/resume leaving half-dead sockets; seeing repeated `*** Error sending message through websocket: {"type"=>"add", ...}` lines in a long-running daemon's log.
Related errors
AI-assisted analysis of sj26/mailcatcher@18a9cb7a79 (2026-08-21).
Data as JSON: /api/errors/e4e1745619db67d2.
Report an issue: GitHub.