fix(server): mail test & retry (#15044)

#### PR Dependency Tree


* **PR #15044** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Stop sending notifications to disabled users; skip member invites when
workspace names contain URLs/domains
* Improve mail retry handling (per-recipient exhaustion, expiry, and
cache cleanup)
  * Make many email headers/lead lines more generic and consistent
  * Fail-safe workspace content parsing to avoid crashes

* **New Features**
* 24-hour signup protection for sharing, invites, and invite-link
creation
  * Job-queue: remove jobs by payload predicate

* **Tests**
* Expanded tests for mail jobs, SMTP hostname handling, payment
checkout, job-queue removal, and abuse-detection utilities
  * Updated test fixtures to set createdAt timestamps for new users

* **Chores**
  * Added required name input for test-email mutation
  * Database flush retry with deadlock detection/backoff

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/toeverything/AFFiNE/pull/15044?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-05-31 00:06:29 +08:00
committed by GitHub
parent 2bd920fea6
commit b05c387f96
27 changed files with 702 additions and 117 deletions
+20 -45
View File
@@ -83,95 +83,70 @@ export const Renderers = {
//#region Workspace
MemberInvitation: make(
Invitation,
props => `${props.user.email} invited you to join ${props.workspace.name}`
'You were invited to join a workspace on AFFiNE'
),
MemberAccepted: make(
InvitationAccepted,
props => `${props.user.email} accepted your invitation`
),
MemberLeave: make(
MemberLeave,
props => `${props.user.email} left ${props.workspace.name}`
'Your workspace invitation was accepted'
),
MemberLeave: make(MemberLeave, 'A workspace member left'),
LinkInvitationReviewRequest: make(
LinkInvitationReviewRequest,
props => `New request to join ${props.workspace.name}`
'New request to join a workspace'
),
LinkInvitationApprove: make(
LinkInvitationApproved,
props => `Your request to join ${props.workspace.name} has been approved`
'Your request to join a workspace has been approved'
),
LinkInvitationDecline: make(
LinkInvitationReviewDeclined,
props => `Your request to join ${props.workspace.name} was declined`
),
MemberRemoved: make(
MemberRemoved,
props => `You have been removed from ${props.workspace.name}`
'Your request to join a workspace was declined'
),
MemberRemoved: make(MemberRemoved, 'You have been removed from a workspace'),
OwnershipTransferred: make(
OwnershipTransferred,
props => `Your ownership of ${props.workspace.name} has been transferred`
'Your workspace ownership has been transferred'
),
OwnershipReceived: make(
OwnershipReceived,
props => `You are now the owner of ${props.workspace.name}`
'You are now the owner of a workspace'
),
//#endregion
//#region Doc
Mention: make(
Mention,
props => `${props.user.email} mentioned you in ${props.doc.title}`
),
Comment: make(
Comment,
props => `${props.user.email} commented on ${props.doc.title}`
),
CommentMention: make(
CommentMention,
props =>
`${props.user.email} mentioned you in a comment on ${props.doc.title}`
),
Mention: make(Mention, 'You were mentioned in AFFiNE'),
Comment: make(Comment, 'New comment in AFFiNE'),
CommentMention: make(CommentMention, 'You were mentioned in a comment'),
//#endregion
//#region Team
TeamWorkspaceUpgraded: make(TeamWorkspaceUpgraded, props =>
props.isOwner
? 'Your workspace has been upgraded to team workspace! 🎉'
: `${props.workspace.name} has been upgraded to team workspace! 🎉`
),
TeamBecomeAdmin: make(
TeamBecomeAdmin,
props => `You are now an admin of ${props.workspace.name}`
: 'A workspace has been upgraded to team workspace! 🎉'
),
TeamBecomeAdmin: make(TeamBecomeAdmin, 'You are now a workspace admin'),
TeamBecomeCollaborator: make(
TeamBecomeCollaborator,
props => `Your role has been changed in ${props.workspace.name}`
'Your workspace role has been changed'
),
TeamDeleteIn24Hours: make(
TeamDeleteIn24Hours,
props =>
`[Action Required] Final warning: Your workspace ${props.workspace.name} will be deleted in 24 hours`
'[Action Required] Final warning: Your workspace will be deleted in 24 hours'
),
TeamDeleteInOneMonth: make(
TeamDeleteInOneMonth,
props =>
`[Action Required] Important: Your workspace ${props.workspace.name} will be deleted soon`
'[Action Required] Important: Your workspace will be deleted soon'
),
TeamWorkspaceDeleted: make(
TeamWorkspaceDeleted,
props => `Your workspace ${props.workspace.name} has been deleted`
'Your workspace has been deleted'
),
TeamWorkspaceExpireSoon: make(
TeamExpireSoon,
props =>
`[Action Required] Your ${props.workspace.name} team workspace will expire soon`
),
TeamWorkspaceExpired: make(
TeamExpired,
props => `Your ${props.workspace.name} team workspace has expired`
'[Action Required] Your team workspace will expire soon'
),
TeamWorkspaceExpired: make(TeamExpired, 'Your team workspace has expired'),
//#endregion
//#region License