Private
Public Access
Also email room subscribers on the first unread message, not just mentions (#67)
The previous pass only emailed on a mention. Corrected scope: the room's first unread message triggers one debounced email (same shape as #66), and every mention additionally emails regardless of that debounce, since a mention shouldn't get silently absorbed by an earlier plain message's already-sent notification. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -249,7 +249,7 @@ async def _maybe_email_dm_notification(
|
||||
)
|
||||
|
||||
|
||||
async def _maybe_email_room_mention_notifications(
|
||||
async def _maybe_email_room_notifications(
|
||||
db: AsyncSession,
|
||||
global_presence: GlobalPresence,
|
||||
base_url: str,
|
||||
@@ -257,11 +257,16 @@ async def _maybe_email_room_mention_notifications(
|
||||
sender: User,
|
||||
message: Message,
|
||||
) -> None:
|
||||
"""#67: opt-in, per-room email on mention -- deliberately scoped to
|
||||
regular rooms only (see set_room_email_notifications: DMs already get
|
||||
#66's always-on offline email, no separate toggle). Debounced the same
|
||||
shape as #66 -- only the first unread mention in this room emails, not
|
||||
one per mention while the recipient is away.
|
||||
"""#67: opt-in, per-room email -- deliberately scoped to regular rooms
|
||||
only (see set_room_email_notifications: DMs already get #66's
|
||||
always-on offline email, no separate toggle).
|
||||
|
||||
Two triggers, not one: the room's first unread message debounces the
|
||||
same way #66 does (one email per unread burst, not one per message),
|
||||
but a mention always emails regardless of that debounce -- a mention
|
||||
is a stronger, individually-addressed signal that shouldn't get
|
||||
silently swallowed just because an earlier plain message in the same
|
||||
burst already used up the "first unread" email.
|
||||
"""
|
||||
room = await db.get(Room, room_id)
|
||||
if room is None or room.is_dm:
|
||||
@@ -270,20 +275,21 @@ async def _maybe_email_room_mention_notifications(
|
||||
result = await db.execute(
|
||||
select(MessageMention.user_id).where(MessageMention.message_id == message.id)
|
||||
)
|
||||
mentioned_ids = {row[0] for row in result.all()} - {sender.id}
|
||||
if not mentioned_ids:
|
||||
mentioned_ids = {row[0] for row in result.all()}
|
||||
|
||||
result = await db.execute(
|
||||
select(RoomMembership).where(
|
||||
RoomMembership.room_id == room_id,
|
||||
RoomMembership.user_id != sender.id,
|
||||
RoomMembership.email_notifications.is_(True),
|
||||
)
|
||||
)
|
||||
subscribed_memberships = result.scalars().all()
|
||||
if not subscribed_memberships:
|
||||
return
|
||||
|
||||
for user_id in mentioned_ids:
|
||||
membership_result = await db.execute(
|
||||
select(RoomMembership).where(
|
||||
RoomMembership.room_id == room_id, RoomMembership.user_id == user_id
|
||||
)
|
||||
)
|
||||
membership = membership_result.scalar_one_or_none()
|
||||
if membership is None or not membership.email_notifications:
|
||||
continue
|
||||
|
||||
for membership in subscribed_memberships:
|
||||
user_id = membership.user_id
|
||||
recipient = await db.get(User, user_id)
|
||||
if recipient is None:
|
||||
continue
|
||||
@@ -291,30 +297,41 @@ async def _maybe_email_room_mention_notifications(
|
||||
if not recipient.appear_offline and await global_presence.is_online(recipient.id):
|
||||
continue
|
||||
|
||||
already_unread_mention = await db.execute(
|
||||
select(Message.id)
|
||||
.join(MessageMention, MessageMention.message_id == Message.id)
|
||||
.where(
|
||||
MessageMention.user_id == user_id,
|
||||
Message.room_id == room_id,
|
||||
Message.id != message.id,
|
||||
Message.created_at > membership.last_read_at,
|
||||
mentioned = user_id in mentioned_ids
|
||||
if not mentioned:
|
||||
already_unread = await db.execute(
|
||||
select(Message.id)
|
||||
.where(
|
||||
Message.room_id == room_id,
|
||||
Message.id != message.id,
|
||||
Message.created_at > membership.last_read_at,
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
if already_unread_mention.scalar_one_or_none() is not None:
|
||||
continue
|
||||
if already_unread.scalar_one_or_none() is not None:
|
||||
continue
|
||||
|
||||
if mentioned:
|
||||
subject = f"New mention in #{room.name}"
|
||||
body_line = (
|
||||
f"{sender.username} mentioned you: {message.content[:200]}"
|
||||
if message.content
|
||||
else f"{sender.username} mentioned you"
|
||||
)
|
||||
else:
|
||||
subject = f"New message in #{room.name}"
|
||||
if message.content:
|
||||
body_line = f"{sender.username}: {message.content[:200]}"
|
||||
elif message.file_id:
|
||||
body_line = f"{sender.username} sent a file"
|
||||
else:
|
||||
body_line = f"{sender.username} sent an image"
|
||||
|
||||
body_line = (
|
||||
f"{sender.username} mentioned you: {message.content[:200]}"
|
||||
if message.content
|
||||
else f"{sender.username} mentioned you"
|
||||
)
|
||||
link = f"{base_url.rstrip('/')}/rooms/{room_id}"
|
||||
await send_email(
|
||||
db,
|
||||
recipient.email,
|
||||
f"New mention in #{room.name}",
|
||||
subject,
|
||||
[body_line],
|
||||
cta_label="Open room",
|
||||
cta_url=link,
|
||||
@@ -361,7 +378,7 @@ async def broadcast_new_message(
|
||||
)
|
||||
await _notify_offline_members(db, broadcaster, presence, focus_presence, room_id, sender, message)
|
||||
await _maybe_email_dm_notification(db, global_presence, base_url, room_id, sender, message)
|
||||
await _maybe_email_room_mention_notifications(db, global_presence, base_url, room_id, sender, message)
|
||||
await _maybe_email_room_notifications(db, global_presence, base_url, room_id, sender, message)
|
||||
await dispatch_event(db, "message.created", room_id, payload)
|
||||
_maybe_fetch_link_preview(broadcaster, room_id, message)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user