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 204

View on GitHub (pinned to 18a9cb7a79)

Solutions

  1. 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.
  2. Confirm the Exception line is a disconnect class (Errno::EPIPE, Errno::ECONNRESET, IOError); anything else (JSON::GeneratorError etc.) deserves investigation.
  3. If the log floods, upgrade the mailcatcher gem — newer websocket handling checks connection state before sending.
  4. 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

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.