Notifications this addon sends

The nine notification families copy trading fires at leaders, followers and administrators — recipients, channels, priorities, deep links, which events never fire, and what you can and cannot reword.

6 min readUpdated 6 August 2026notifications, email, push, templates, audit

Copy trading sends nine distinct families of notification. They are separate from the audit log: a notification is what the customer saw, an audit row is what happened. A notification can fail to deliver and leave no trace on the customer's side while the audit row is written regardless — that asymmetry is the reason this page exists.

All nine live in one file and all nine go through the platform notification service, so they inherit its per-user preferences, its channel filtering and its queueing.

The nine families

Family Recipient Events
notifyLeaderApplicationEvent The applicant / leader APPLIED APPROVED REJECTED SUSPENDED ACTIVATED
notifyLeaderNewFollower The leader Somebody subscribed
notifyLeaderFollowerStopped The leader A follower stopped
notifyFollowerSubscriptionEvent The follower STARTED PAUSED RESUMED STOPPED FORCE_STOPPED
notifyFollowerAllocationEvent The follower CREATED FUNDS_ADDED FUNDS_REMOVED INSUFFICIENT_BALANCE
notifyFollowerTradeEvent The follower COPIED CLOSED FAILED PROFIT LOSS
notifyFollowerRiskEvent The follower DAILY_LOSS_LIMIT POSITION_SIZE_LIMIT AUTO_PAUSED AUTO_STOPPED
notifyProfitShareEvent Leader or follower EARNED RECEIVED DISTRIBUTED
notifyCopyTradingAdmins Every administrator LEADER_APPLICATION SUSPICIOUS_ACTIVITY HIGH_LOSS_FOLLOWER LEADER_SUSPENDED

Copy trading also sends four notifications outside this system, through the core createNotification helper — the automatic daily-limit resume, the daily-loss pause, the backstop settler's trade-closed message, and the admin reactivation message. Those are in-app only.

Leader application events

The five events all carry relatedId = the leader id and deep-link to /copy-trading/leader/me, except suspension, which links to /support.

Event Type Priority Channels Fired by
APPLIED SYSTEM NORMAL IN_APP, PUSH, EMAIL POST /api/copy-trading/leader/apply
APPROVED SYSTEM NORMAL IN_APP, PUSH, EMAIL The approve route, and apply when auto-approve is on
REJECTED ALERT HIGH IN_APP, PUSH, EMAIL POST /api/admin/copy-trading/leader/{id}/reject
SUSPENDED ALERT URGENT IN_APP, PUSH, EMAIL POST /api/admin/copy-trading/leader/{id}/suspend
ACTIVATED SYSTEM NORMAL IN_APP, PUSH Never fired — see below

The rejection reason you type into the reject dialog is carried into the message the applicant reads: "Your leader application was not approved. Reason: …". With no reason supplied the message falls back to "Please contact support for details." The same is true of a suspension reason, which the suspend endpoint makes mandatory.

Write those reasons as if the customer will read them, because they will.

POST /api/admin/copy-trading/leader/{id}/activate does not call this family at all. It sends its own in-app-only notification titled "Copy Trading Account Reactivated", linking to /user/copy-trading/leader.

So a reactivated leader gets no email and no push, and lands on a link that does not resolve to a page (see the deep-link table below). Tell a reinstated leader directly.

Follower subscription events

Deep-link to /copy-trading/follower/{followerId}, except FORCE_STOPPED, which links to /support.

Event Type Priority Channels Fired by
STARTED SYSTEM NORMAL IN_APP, PUSH, EMAIL POST /api/copy-trading/follower/follow
PAUSED ALERT NORMAL IN_APP, PUSH, EMAIL The follower's own pause, the admin pause, and every follower of a suspended leader
RESUMED SYSTEM NORMAL IN_APP, PUSH, EMAIL The follower's own resume
STOPPED ALERT HIGH IN_APP, PUSH, EMAIL The follower's own stop
FORCE_STOPPED ALERT HIGH IN_APP, PUSH, EMAIL Never fired — see below

STOPPED and FORCE_STOPPED both load the subscription's statistics before sending, so the message costs an extra computation; everything else is a plain read.

The suspension gap you have to plan around

This is the single most consequential behaviour on this page.

Suspending a leader pauses every follower behind them and sends each one a PAUSED notification reading "Leader suspended: ". The leader gets an URGENT alert. Administrators get a LEADER_SUSPENDED alert.

Reactivating that leader sends nothing to any follower, and does not resume them. resetCopyTradingDailyLimits — the only job that lifts a pause automatically — deliberately skips any follower whose leader is not ACTIVE, and even then only lifts a pause whose most recent audit action is DAILY_LOSS_LIMIT_REACHED from a previous UTC day. A pause caused by a suspension is recorded against the leader, not the follower, so nothing will ever lift it.

Every one of those followers stays paused, with money still sitting in their COPY_TRADING wallet, until each of them resumes it or you resume it for them at /admin/copy-trading/follower. Plan the reactivation announcement before you suspend a leader with a book.

The bulk endpoint is worse in one specific way. POST /api/admin/copy-trading/leader/bulk/status with status: "SUSPENDED" pauses every affected follower exactly as the single suspend does, but sends no notification to anybody — not the leaders, not the followers, not administrators. It only writes BULK_SUSPENDED audit rows. If you use it on a backlog, do the customer communication yourself.

The families with no call sites

Three of the nine are written, exported and unreachable:

Family State
notifyFollowerAllocationEvent No call site anywhere. Adding, removing or exhausting allocation funds notifies nobody
notifyFollowerRiskEvent No call site. The daily-loss pause message a follower actually receives comes from the cron's own createNotification, with different wording
notifyProfitShareEvent (RECEIVED, DISTRIBUTED) Only EARNED fires, from binary settlement, to the leader. Followers are never told a profit share was taken

notifyFollowerTradeEvent fires only on the binary path — COPIED when a binary copy is placed, then PROFIT, LOSS or CLOSED at settlement according to the sign of the P&L. FAILED has no call site. Spot copies produce no per-trade notification from this family at all; a spot follower is told only when the backstop settler closes their trade, through a separate in-app message.

Do not build an operating procedure on a customer being told about an allocation change, a risk pause, or a profit share deduction.

Channels, priorities and preferences

Requested channels are filtered per user before anything is sent. The defaults on a user who has never touched their notification settings are:

Channel Default Effect
IN_APP Always allowed The bell, plus a live WebSocket push when they are online
EMAIL On Queued for async delivery
PUSH Off Silently dropped unless the user has enabled push and registered a device
SMS Off Copy trading never requests it

So the practical shipped behaviour is in-app plus email, with push reaching only users who opted in. Every family requests IN_APP and PUSH; the leader-application and follower-subscription families additionally request EMAIL, as do the two leader-facing follower events. The allocation, trade, risk, profit-share and admin families are in-app and push only — they are never emailed.

priority (LOW / NORMAL / HIGH / URGENT) only sets the email queue's job priority and is stored on the row. type (SYSTEM or ALERT) is the axis that customers can filter on: a user who has switched the system notification type off in their preferences will not receive the SYSTEM half of this list at all. Note that copy trading never tags these with the COPY_TRADING notification type, so a per-product opt-out is not available to them.

What you can and cannot reword

Templates are edited at Admin → System → Notifications → Templates (/admin/system/notification/template), and the email renderer looks a template up by name in notification_template, only using rows where the email flag is on.

The copy-trading notification code names ten email templates — copyTradingLeaderApplication, copyTradingLeaderApproved, copyTradingLeaderRejected, copyTradingLeaderSuspended, copyTradingNewFollower, copyTradingFollowerStopped, copyTradingSubscriptionStarted, copyTradingSubscriptionPaused, copyTradingSubscriptionResumed and copyTradingSubscriptionStopped.

None of them are seeded, and the email channel reads a differently-named field than the one copy trading populates, so the lookup never happens. Every copy-trading email is therefore rendered by the generic fallback builder from the notification's own title and message — the same two strings the in-app bell shows.

You cannot change that wording from the admin panel. Creating rows with those names will not take effect either. The only operator-controlled text in any of these messages is the reason you type on a reject, a suspend or an admin stop.

Everything else — the titles, the sentences, the deep links — is in copy-trading/utils/notifications.ts and changing it is a code change.

The links carried on these notifications were written against a route layout that does not match the shipped frontend. Verify before you tell a customer to "click the link in the notification".

Link used Resolves
/copy-trading/subscription Yes — used by the two cron notifications
/support Yes — used by suspension and force-stop
/admin/copy-trading, /admin/copy-trading/leader/{id}, /admin/copy-trading/follower/{id}, /admin/copy-trading/audit Yes — the administrator links are all correct
/copy-trading/leader/me No — matches the leader/[id] route with the literal id me and renders a missing leader
/copy-trading/leader/followers, /copy-trading/leader/earnings No — same problem
/copy-trading/follower/{id}, /copy-trading/follower/{id}/trades, /copy-trading/follower/history No — there is no follower route
/copy-trading/earnings, /copy-trading/subscriptions No — the real page is /copy-trading/subscription
/user/copy-trading/leader No — used by the admin reactivation message

The notification text itself is accurate in every case; it is only the destination that is wrong. Point customers at /copy-trading/dashboard and /copy-trading/subscription.

The administrator channel

notifyCopyTradingAdmins resolves its recipients by role permission: every user whose role carries access.copy_trading. Add that permission to a role and its members start receiving these; remove it and they stop. There is no separate notification opt-in list.

Event Priority Link Fired by
LEADER_APPLICATION NORMAL /admin/copy-trading/leader/{id} A new leader application
LEADER_SUSPENDED NORMAL /admin/copy-trading/leader/{id} The single suspend endpoint
SUSPICIOUS_ACTIVITY HIGH /admin/copy-trading/audit No call site
HIGH_LOSS_FOLLOWER HIGH /admin/copy-trading/follower/{id} No call site

All are sent as ALERT, in-app and push only. There is no administrator email for any copy-trading event.

copyTradingAutoApproveLeaders makes the apply endpoint skip notifyCopyTradingAdmins entirely — the applicant is told they were approved, and no administrator is told anything. That is not a bug, it is the point of the setting, but it means your only record of a new leader is the audit log and the pending-free leader list.

If you run with auto-approve on, put a recurring check of /admin/copy-trading/leader in your routine. See Settings.

Failures are swallowed

Every one of the nine families wraps its whole body in try/catch, logs the failure under the copy-trading module, and returns normally. The admin fan-out catches per-recipient as well, so one bad administrator row does not stop the rest.

That is the right behaviour — a failed notification must never roll back a suspension or a subscription — but it has an operational consequence:

If SMTP credentials are wrong, if the notification queue is not draining, or if the email channel's configuration check is failing, nothing on any copy-trading screen will tell you. Suspensions still complete, subscriptions still start, audit rows still land, and every admin action reports success.

The signals are outside this addon: the backend log, and the email queue on Admin → System → Notifications. Check both after any change to mail configuration, and treat "customers say they got no email" as a mail-infrastructure report rather than a copy-trading one.

One more consequence of how these are keyed: every family builds its idempotency key with the current timestamp appended, so the notification service's duplicate suppression can never match. Two suspensions of the same leader a second apart produce two notifications. This is not a setting.

Notifications versus the audit trail

They answer different questions and they are not interchangeable.

Notification copy_trading_audit_logs
Records What the customer was told What the platform did
Written when delivery fails The in-app row still lands; email and push do not Always
Carries the acting admin No Yes, in adminId
Carries old and new values No Yes, as JSON
Carries the reason Only inside the message text Yes, in its own column
Survives the customer deleting it No Yes

When you are reconstructing an incident, the audit log at /admin/copy-trading/audit is the record. Use the notification only to answer "did we tell them, and what did we say". Remember that two spellings of the follower entity type are in use — copyTradingFollower from the engine and FOLLOWER from the user-facing routes — so filter for both.