I am not able to send to receiver_id user. message_details is a table field
import frappe
from frappe.model.document import Document
class CandidatesChatMessage(Document):
def after_insert(self):
# Check if the message_details child table has any rows
if self.message_details:
for row in self.message_details:
if row.receiver_id:
frappe.publish_realtime(
event=“candidate_receive_message”,
message={
“sender_id”: row.sender_id,
“message”: row.message
},
user=row.receiver_id, # Send event to this receiver,
after_commit=True
)
there are a couple of common pitfalls with realtime events in ERPNext/Frappe that match your code.
Here’s the breakdown:
1. user in publish_realtime must be a valid system user ID
In Frappe, the user parameter must exactly match the User.name (usually the email like test@example.com), not a custom field like an employee ID, candidate ID, or numeric PK.
If your receiver_id is coming from message_details and is not the actual User.name from tabUser, the message will never reach the target.
Fix:
Before calling publish_realtime, make sure row.receiver_id is the actual frappe.get_doc("User", ...).name.
And make sure the socket is connected — if using a desk page, it’s automatic; for website context, you need to enable it.
3. after_commit=True requires transaction commit
Since you are using after_insert (which happens before the DB commit), after_commit=True is correct — but the actual push will only happen after the database transaction finishes.
If you’re testing in a script runner or in a failed transaction, the realtime event won’t trigger.
4. Possible mismatch with child table values
If your message_details table is empty or receiver_id is None at after_insert, nothing will fire. In many cases, child tables aren’t yet populated at after_insert if they’re added after save in UI scripts.
import frappe
from frappe.model.document import Document
class CandidatesChatMessage(Document):
def after_insert(self):
if self.message_details:
for row in self.message_details:
if row.receiver_id:
# Make sure receiver_id is an actual frappe User.name
user_id = frappe.db.get_value("User", row.receiver_id, "name") \
or row.receiver_id
frappe.publish_realtime(
event="candidate_receive_message",
message={
"sender_id": row.sender_id,
"message": row.message
},
user=user_id,
after_commit=True
)
If your frappe.publish_realtime works for general broadcasts but not when specifying user=row.receiver_id, there are a few common gotchas in ERPNext/Frappe that you should check:
The user parameter expects a Frappe system user ID
If receiver_id is user@example.com, ensure:
That exact string exists in User → Email in ERPNext.
The user is enabled and has logged in at least once (so the WebSocket session is active).
If you have a custom field storing a user’s email, make sure it’s exactly the same as frappe.session.user for that account.
WebSocket connection scope
publish_realtime sends messages only to connected WebSocket sessions.
If the user is not currently logged in or not on a page that subscribes to that event, they won’t receive it.
Test by opening two browser tabs:
Tab 1 → logged in as sender.
Tab 2 → logged in as receiver_id.
Trigger the code in Tab 1, check console in Tab 2.
Client-side listener
Your frontend must subscribe to the same event name with the correct handler:
javascript
frappe.realtime.on("candidate_receive_message", function (data) {
console.log("Message received:", data);
frappe.utils.play_sound("email");
});
After commit timing
Since you’re using after_commit=True, the event will fire after the DB transaction is committed.
If you’re testing in after_insert, this should work fine — but try without after_commit=True to rule out transaction issues.
Using message_details table
If message_details is a child table, make sure the loop actually runs and row.receiver_id is not empty or different than expected.
python
for row in self.message_details:
frappe.logger().debug(f"Publishing to: {row.receiver_id}")
Alternative approach for testing
If direct user targeting fails, test a broadcast to verify if the WebSocket link works at all:
basically means your browser is trying to connect to connecting.localhost, which is not a valid hostname.
It’s not an ERPNext-specific error — it’s coming from the socket.io client because the socket URL is wrong.
Why this happens in ERPNext
ERPNext’s realtime events (e.g., frappe.realtime.publish / frappe.realtime.on) use a WebSocket connection to the site’s URL.
If site_config.json or your host_name is wrong, or if you’re running from a different origin (like a tunnel or VM), the client might build an incorrect URL like connecting.localhost instead of localhost or your actual domain.
Checklist to fix it
Check site_config.json
Inside your site folder (sites/[yoursite]/site_config.json), make sure you have:
@management
I’m having the same issue, the problem that i found in my case was the following
I’m developing my site in react with vite, and using vite proxy to the server, for example from localhost port 3000 to 8000
And the socket io server, validates the origin and the host to match, and even if you try to change the origin header, it won’t work.
I haven’t figure it out
You need to connect to socketio as in new versions of frappe socket is not connected by defaulted. I connected to socketio in bench then it got resolved