GMI · TECHNOLOGY OBSERVATORY // ALL SYSTEMS NOMINAL
ENGINEERED BY LEOPARD DATA

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?"

TELEMETRY FLOW
rendering diagram…
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;
Three-layer telemetry: user actions trigger admin emails, activity log writes, and GA4 events in parallel.
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
1. User Action
User performs an operation (e.g., generates a report)
2. Service Layer
IAdminNotificationEmailService builds an HTML email
3. Email Queue
Email queued in the emailstosend table for delivery
4. Delivery
EmailSender service delivers to all active admin emails
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:

  1. Retrieve all active admin email addresses from the adminnotificationemailaddresses table
  2. Build a styled HTML email body with the operation details (all user input is HTML-encoded to prevent XSS)
  3. Queue the email in the emailstosend table with IsSent = false
  4. 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
PageView

Logged via LogPageViewAsync when a user navigates to a page.

  • ActivityName: Page route (e.g., /manage-folders)
  • ActivityDetail: null
  • PageContext: null
LinkClick

Logged via LogLinkClickAsync when a user clicks a navigation link or external URL.

  • ActivityName: Link label
  • ActivityDetail: Target URL
  • PageContext: Current page route
Operation

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)