Platform Telemetry
Real-time operational visibility through admin email notifications and activity logging
Telemetry Architecture
Grade My Investments implements a dual-layer telemetry system that gives administrators near real-time visibility
into how the platform is being used. The first layer sends automated email notifications to all admin users
whenever significant operations occur, such as report generation, payment method changes, or file management
activities. The second layer captures granular user interactions, including every button click, link click,
and page view, and persists them to the activitylogentries table for analysis.
A third layer — Google Analytics 4 (property G-F04E5SVW3D) — is wired into the Client Web
app to capture aggregate traffic, acquisition, and audience data. GA4 complements the in-database activity log:
GA4 answers "who is finding us and where are they coming from?", while the activity log answers
"what did this specific signed-in user do, and when?"
flowchart TB
UA[User Action<br/>ClientWeb - AdminWeb - MobileApp]:::app
subgraph L1[Layer 1 - Admin Email]
ANS[IAdminNotificationEmailService<br/>builds HTML email]:::svc
EQ[(emailstosend table<br/>IsSent false)]:::data
ES[EmailSender<br/>background service]:::svc
ADM[Admin Inboxes<br/>real-time]:::ext
end
subgraph L2[Layer 2 - Activity Log]
ALS[IActivityLogEntryService<br/>fire-and-forget]:::svc
AL[(activitylogentries<br/>MySQL)]:::data
end
subgraph L3[Layer 3 - GA4]
GA[Google Analytics 4<br/>G-F04E5SVW3D]:::ext
end
UA --> ANS
ANS --> EQ
EQ --> ES
ES --> ADM
UA --> ALS
ALS --> AL
UA --> GA
classDef app fill:#1f2630,stroke:#C0894E,color:#F3EFE8;
classDef svc fill:#241c14,stroke:#E2B583,color:#F3EFE8;
classDef data fill:#22201a,stroke:#C0894E,color:#EBD3AE;
classDef ext fill:#1b1f27,stroke:#5A616B,color:#C4C0B8;
classDef sec fill:#1d2733,stroke:#5cc8e0,color:#eafaff;
Admin Email Notifications
Real-time emails to all admins when users perform key operations
Activity Logging
Every button click, link click, and page view logged to the database
Persistent Storage
All telemetry stored in MySQL for historical analysis and audit trails
Admin Email Notifications
When a user performs a significant operation on the platform, Grade My Investments automatically queues an HTML email notification to every active admin notification email address. These emails are sent by the EmailSender background service and arrive in admin inboxes within seconds, providing a near real-time feed of platform activity.
How It Works
Account Notifications
| Event | Email Subject | Details Included |
|---|---|---|
| New Account Signup | New Account Signup - Gmi Admin Notification | Name, email, account ID, signup date |
| Account Updated | Account Updated - Needs Validation - Gmi Admin Notification | Name, email, account ID, validation status |
| Payment Method Added | Payment Method Added - Gmi Admin Notification | Name, email, masked card (last 4), expiration, default status, country |
| AI Dashboard Viewed | AI Dashboard Viewed - Gmi Admin Notification | Account info, job ID, symbol count, app source (ClientWeb or MobileApp) |
| Dashboard Claude Analysis | Dashboard Claude Analysis - Gmi Admin Notification | Account info, job ID, dashboard level, question, cost, app source |
| Critical Error | CRITICAL ERROR - {ServiceName} - {ErrorType} - Gmi Alert | Service name, error type, error message, stack trace, job ID, account ID |
Symbol List & Report Notifications
| Event | Email Subject | Details Included |
|---|---|---|
| Symbol List Created | Symbol List Created - Gmi Admin Notification | Account info, list name, description, symbol count, full list of symbols |
| Report Generation Requested | Report Generation Requested - Gmi Admin Notification | Account info, job ID, timestamp, symbol list details with contents, total symbol count |
File & Folder Management Notifications
| Event | Email Subject | Details Included |
|---|---|---|
| Folder Created | Folder Created - Gmi Admin Notification | Account info, folder name, parent folder or root level |
| Folder Renamed | Folder Renamed - Gmi Admin Notification | Account info, old name, new name |
| Folder Color Changed | Folder Color Changed - Gmi Admin Notification | Account info, folder name, new color with color swatch |
| Folder Moved Up/Down | Folder Moved Up/Down - Gmi Admin Notification | Account info, folder name, direction |
| Folder Deleted | Folder Deleted - Gmi Admin Notification | Account info, folder name |
| Folder Download Requested | Folder Download Requested - Gmi Admin Notification | Account info, folder name, file count |
| File Uploaded | File Uploaded - Gmi Admin Notification | Account info, file name, folder name, file size |
| File Downloaded | File Downloaded - Gmi Admin Notification | Account info, file name, folder name |
| File Renamed | File Renamed - Gmi Admin Notification | Account info, old name, new name, folder name |
| File Color Changed | File Color Changed - Gmi Admin Notification | Account info, file name, folder name, new color with swatch |
| File Deleted | File Deleted - Gmi Admin Notification | Account info, file name, folder name |
Implementation Details
All admin notification emails are sent through the IAdminNotificationEmailService interface, which follows this pattern:
- Retrieve all active admin email addresses from the
adminnotificationemailaddressestable - Build a styled HTML email body with the operation details (all user input is HTML-encoded to prevent XSS)
- Queue the email in the
emailstosendtable withIsSent = false - The EmailSender background service picks up queued emails and delivers them
Notifications are fire-and-forget by design. If a notification fails to queue, the operation still succeeds and a warning is logged. This ensures that email delivery issues never block user operations.
Activity Logging
Grade My Investments captures granular user interaction data across all client-facing applications. Every page view,
button click, and link click is logged to the activitylogentries table in MySQL, providing a
complete picture of how users navigate and interact with the platform.
ActivityLogEntries Table Schema
| Column | Type | Description |
|---|---|---|
ActivityLogEntryID |
bigint unsigned (PK, auto-increment) | Unique identifier for each log entry |
AccountID |
bigint unsigned (FK) | The user account that performed the action |
ActivityType |
varchar(20) | Category of activity: PageView, LinkClick, or Operation |
ActivityName |
varchar(500) | Name of the activity (e.g., page route, button label, operation name) |
ActivityDetail |
varchar(500), nullable | Additional details (e.g., URL for link clicks, parameters for operations) |
PageContext |
varchar(500), nullable | The page the user was on when the activity occurred |
ActivityDateTime |
datetime | UTC timestamp of when the activity occurred |
AppSource |
varchar(20) | Which application generated the entry: ClientWeb, AdminWeb, or MobileApp |
Activity Types
Logged via LogPageViewAsync when a user navigates to a page.
- ActivityName: Page route (e.g.,
/manage-folders) - ActivityDetail: null
- PageContext: null
Logged via LogLinkClickAsync when a user clicks a navigation link or external URL.
- ActivityName: Link label
- ActivityDetail: Target URL
- PageContext: Current page route
Logged via LogOperationAsync when a user performs a business action.
- ActivityName: Operation name (e.g.,
CreateSymbolList) - ActivityDetail: Operation parameters
- PageContext: Current page route
Instrumented Pages & Operations
The following pages in the Client Web application are instrumented with activity logging:
| Page | Page Views | Operations Logged |
|---|---|---|
Manage Folders (/manage-folders) |
Folder create, rename, delete, color change, move, file upload, download, rename, delete, color change | |
Manage Symbol Lists (/symbollists) |
Symbol list create, update, delete, add symbol | |
Add/Edit Symbol List (/symbols/add, /symbols/edit) |
Create symbol list, update symbol list | |
Generate Report (/reports/generate) |
Report generation request | |
Report Generations (/reports) |
View report jobs | |
Account Settings (/account) |
Update account | |
Billing & Usage (/account/billing) |
View billing data | |
Payment Methods (/account/payment) |
Add/remove payment methods | |
Report Dashboard (/report-dashboard) |
View symbol detail, view financial detail, ask Claude AI | |
Getting Started (/getting-started) |
Page view, CTA link clicks | |
Email Notifications (/notifications/emails) |
Add/remove email addresses | |
SMS Notifications (/notifications/sms) |
Add/remove SMS numbers | |
Create Account (/account/create) |
Account creation | |
Account Setup Payment (/account/setup-payment) |
Initial payment method setup | |
Add Payment Method (/account/payment/add) |
Payment method form submission | |
Validate Email (/account/validate-email) |
Not yet instrumented | |
Dashboard (/dashboard) |
Page view, quick action link clicks |
IActivityLogEntryService Interface
Activity logging is exposed through a clean service interface with convenience methods for each activity type:
public interface IActivityLogEntryService
{
Task LogActivityAsync(ulong accountId, string activityType, string activityName,
string? activityDetail = null, string? pageContext = null,
string appSource = "ClientWeb");
Task LogPageViewAsync(ulong accountId, string pageName,
string appSource = "ClientWeb");
Task LogLinkClickAsync(ulong accountId, string linkName,
string? url = null, string? pageContext = null,
string appSource = "ClientWeb");
Task LogOperationAsync(ulong accountId, string operationName,
string? detail = null, string? pageContext = null,
string appSource = "ClientWeb");
Task<IEnumerable<ActivityLogEntry>> GetByAccountIdAsync(
ulong accountId, int limit = 50);
Task<IEnumerable<ActivityLogEntry>> GetByAccountIdAndTypeAsync(
ulong accountId, string activityType, int limit = 50);
Task<ActivityLogStats> GetActivityLogStatsAsync();
Task<(List<ActivityLogEntry> items, int totalCount)> GetActivityLogWithFilterAsync(
ulong? accountId = null, string? activityType = null,
string? appSource = null, DateTime? fromDate = null,
DateTime? toDate = null, string? searchText = null,
int pageSize = 20, int pageNumber = 1);
Task<List<(ulong AccountId, int Count)>> GetTopAccountsByActivityAsync(
int top, DateTime sinceDate);
}
All logging calls are fire-and-forget using _ = ActivityLogService.LogPageViewAsync(...)
to ensure that logging never blocks or slows down the user experience. If a log entry fails to write,
the user's operation is unaffected.
Telemetry at a Glance
19
Admin Email Notification Types
3
Activity Log Types (PageView, LinkClick, Operation)
17
Instrumented Client Web Pages
100%
Fire-and-Forget (Zero User Impact)