01About this guide
This guide is for IT administrators and Microsoft 365 / Entra owners who set up and maintain Orbit: the app registration, the technician group, the SharePoint lists, email notifications, Azure Functions and security settings. Names such as the tenant, resource group and group IDs are shown as placeholders. The Server Agent Monitor has its own manual, and who can use each app is set in Settings > Users & Apps.
02Architecture Overview
Orbit is a single-page HTML application hosted on Azure Static Web Apps at crm.pnw.com. It is published from the PNWTimetracking/relax-apps repository by deploy.ps1, which runs as the last step of ClaudeAi\1-BumpAndDeploy.bat. It uses:
- Microsoft Authentication Library (MSAL.js 2.38.3) for delegated sign-in โ no client secret or server component required.
- Microsoft Graph API (v1.0) for all data operations.
- SharePoint Online lists (on the your-tenant.sharepoint.com tenant) for persistent storage.
- Azure AD security groups for technician team membership.
- A shared mailbox (help@pnw.com) for outbound notification emails.
- Three Azure Function apps for the work a browser should not do: relax-monitor-ingest (server agents and monitor emails), relax-kb-api (Knowledge Base, Claude AI, Quotes, command signing) and relax-portal-api (HelpDesk client portal). See Azure Functions below.
The browser uses delegated (user) tokens and holds no secrets or API keys. Server-side work runs in the Azure Functions, which have their own app identities, limited to what they need (Sites.Selected on the team site, Exchange application access policies on mail). Permissions are granted with admin consent.
03Azure AD App Registration
App Details
| Property | Value |
|---|---|
| App name | Relax-CRM |
| Supported account types | Single tenant (mail247us / PNW) |
| Redirect URI | https://crm.pnw.com/ (SPA type; the app uses its own address) |
| Client secret | None โ delegated auth only |
| Application (client) ID | <group Object ID> |
| MSAL.js | 2.38.3 (loaded in the page) |
Required API Permissions
The CRM requests only the four Delegated permissions below (GRAPH_SCOPES in index.html). Admin consent must be granted for each.
| Permission | Type | Purpose |
|---|---|---|
| Sites.Manage.All | Delegated | Read and write SharePoint lists (tickets, contacts, config, comments) |
| GroupMember.Read.All | Delegated | List member IDs from the PNW-Team security group |
| User.ReadBasic.All | Delegated | Fetch each member's display name, mail, and userPrincipalName by user ID |
| Mail.ReadWrite.Shared | Delegated | Send notification emails from the help@pnw.com shared mailbox |
Do not add Mail.Send.Shared to GRAPH_SCOPES in Orbit code. Mail.ReadWrite.Shared is enough to send from help@pnw.com, and requesting Mail.Send.Shared caused token acquisition failures.
The tenant consent list for Relax-CRM also shows permissions from earlier builds that Orbit no longer requests (Sites.ReadWrite.All, Mail.Read.Shared, Mail.Send.Shared, Group.Read.All, Files.Read.All, User.Read). Audit-CrmPermissions.ps1 (ClaudeAi\CRM\) lists them. They can be removed in a later clean-up; test sign-in, email and the Knowledge Base after removing any.
Granting Admin Consent
After adding any new permission: Entra admin center โ App registrations โ Relax-CRM โ API permissions โ Grant admin consent for PNW โ Yes.
After granting new consent, users must sign out of Orbit and sign back in to receive an updated token.
04Tech Team Group Setup
Group Requirements
The Assigned To dropdown in tickets is populated from an Azure AD security group. The group must be:
- Type: Security (not a Microsoft 365 group, distribution list, or dynamic distribution group).
- Membership: Assigned (static) or Dynamic rule โ both work.
- Source: Cloud (on-prem synced groups also work).
Dynamic Distribution Groups are Exchange-only objects. The Microsoft Graph API cannot enumerate their members. Use a Security group instead.
How Member Loading Works
When Orbit loads the tech team, it performs a two-step process:
- GET /groups/{id}/members?$select=id,displayName โ retrieves the list of member Object IDs.
- For each member ID, GET /users/{id}?$select=displayName,mail,userPrincipalName,otherMails โ fetches the full user profile.
This two-step approach is necessary because the group members endpoint returns null for mail and userPrincipalName on on-premises-synced accounts. Fetching /users/{id} directly always returns populated profile fields.
Configuring the Group in CRM Settings
- In the Entra admin center โ Groups, find PNW-Team.
- Click Overview and copy the Object ID (GUID format).
- In Orbit, go to Settings โ Tech Team Group ID.
- Paste the Object ID and click Save & Load.
- The status line shows the result. Success displays: โ 13 members loaded: Name, Name, ...
Adding Members to PNW-Team
Entra admin center โ Groups โ PNW-Team โ Members โ + Add members. Add each technician by name or email. Keep test and demo accounts out of PNW-Team.
After adding members, click ๐ Reload Group Members in CRM Settings to refresh the dropdown without a page reload.
05SharePoint Lists
The CRM keeps its data in SharePoint lists on the your-tenant.sharepoint.com team site. Most are created automatically on first use. The demo site uses the same lists with a Demo_ prefix (Demo_CRM_Tickets and so on), so demo data never mixes with real data.
| List Name | Contents |
|---|---|
| CRM_Config | Single row storing all system settings: client list path, group ID, tech emails, escalation phone, blocked senders. |
| CRM_Contacts | Contact records: name, email, phone, title, linked client. |
| CRM_Tickets | Support tickets: number, subject, description, client, contact, priority, status, assigned-to (email), timestamps. |
| CRM_TicketComments | Comments linked to tickets by ticket ID. |
| CRM_IgnoreRules | Always Ignore Rules (added May 2026): Domain + SubjectKeywords pairs that suppress matching emails without creating tickets. Managed via Settings โ Always Ignore Rules or the ๐ button on any ticket. |
| CRM_BackupMonitor | Parsed Windows Backup digest events. The system auto-creates tickets on 2+ consecutive failures and auto-closes them when backup becomes healthy. |
| CRM_DuoLockouts | Parsed Duo lockout alert emails. |
| CRM_SyncErrors | Parsed Microsoft Entra (Azure AD) sync error emails. |
| CRM_Notes | Client notes. |
| CRM_RecurringTickets | Recurring ticket schedules (with an optional Knowledge Base procedure). |
| CRM_TicketsArchive | Closed tickets moved out of CRM_Tickets to keep it fast. |
| CRM_CannedResponses | Saved reply texts. |
| CRM_Vendors | Links tab: vendor portals and tools by category. |
| CRM_ClientDomains | Maps email and agent domains to clients (agent cards group under the client). |
| CRM_ScMonitor | ScreenConnect monitor events. |
| CRM_AgentHeartbeats | One row per monitored server (Title = server@domain): latest heartbeat, alerts, pending commands, firewall readiness. |
| CRM_DiskAlerts | Disk alerts raised by the agents, with Resolved state. |
| CRM_AgentEnrollments | Agent enrollments and keys (one per server). |
| CRM_KnowledgeBase | Older Knowledge Base list. Articles and files now live in Azure Storage behind relax-kb-api (tables KbArticles, KbFiles, KbConfig, KbAudit; blob container kb-files). Quotes use tables Quotes, QuoteFiles and QuoteLog. |
Client List
The client list is seeded from a TemplateClientList.xlsx file on SharePoint. The default path is:
Public/Public/billing/Contractors/TemplateClientList.xlsx
To use a different file, update the Client List Path in Settings. The path is relative to the SharePoint site root.
06Email Notifications
How It Works
When a ticket is assigned or reassigned to a technician, Orbit sends an HTML email from help@pnw.com to the assigned tech's email address. The email contains:
- Ticket number and subject.
- Client name and priority.
- Full description.
- A direct link to crm.pnw.com.
Clients also get a branded email when their ticket moves to In Progress or Closed. The demo account never sends client emails. Quotes are emailed from quotes@pnw.com by relax-kb-api (see Azure Functions).
Technical Details
Emails are sent via the Microsoft Graph API sendMail endpoint:
POST /users/help@pnw.com/sendMail
This requires the Mail.ReadWrite.Shared delegated permission and the signed-in user must have Send As or Send on Behalf access to the help@pnw.com shared mailbox.
Mail.Send.Shared is intentionally left out of GRAPH_SCOPES. Mail.ReadWrite.Shared is sufficient for sending from shared mailboxes, and requesting Mail.Send.Shared caused token acquisition failures.
07Troubleshooting
| Symptom | Resolution |
|---|---|
| "0 members loaded" after setting group ID | Verify the group is a Security group (not a Dynamic Distribution Group). Check that GroupMember.Read.All and User.ReadBasic.All are granted with admin consent. Sign out and back in after granting new permissions. |
| Assigned To dropdown shows only your name | Group ID not configured in Settings, or group has no members in Azure AD. Check Settings โ Tech Team Group ID status line. |
| 403 on /users/{id} calls | User.ReadBasic.All permission is missing or not admin-consented. Add it in Azure Portal โ App Registrations โ API Permissions. |
| Token errors on sign-in | A scope in GRAPH_SCOPES is not in the app registration. Ensure all four scopes are registered. Never add Mail.Send.Shared. |
| Email notifications not sending | Confirm Mail.ReadWrite.Shared is consented. Confirm the signed-in user has Send As or Send on Behalf rights on help@pnw.com shared mailbox. |
| SharePoint 403 on list operations | Sites.Manage.All not consented, or the signed-in user does not have access to the mail247us SharePoint site. |
| Client dropdown empty | Check Settings โ Client List Path. The TemplateClientList.xlsx must exist at the configured path on SharePoint. |
08Azure Functions and Server Settings
All three Function apps are in the MCPP subscription, resource group your-resource-group. Secrets live only in their app settings, never in the web pages.
| Component | Details |
|---|---|
| relax-monitor-ingest | Server agents (heartbeat, download, enrollment, commands), disk / USB / reboot alerts, and the Windows Backup, Duo and sync-error monitor emails. |
| relax-kb-api | Knowledge Base (Azure Blob + Table Storage, Azure AI Search, text from photos and PDFs), Claude AI for Orbit (ANTHROPIC_API_KEY; per-hour limits AI_PER_HOUR and AI_DEMO_PER_HOUR), Quotes (email from quotes@pnw.com through the app Relax-CRM-Quotes-Mail, limited to the group Relax-QuotesSenders), agent command signing and app-secret expiry warnings. |
| relax-portal-api | HelpDesk client portal; Sites.Selected write on the team site. |
Admin scripts
| Component | Details |
|---|---|
| 1-BumpAndDeploy.bat | ClaudeAi\. Deploys relax-monitor-ingest and publishes the web apps (see Releasing). |
| Deploy-KB-Function.ps1 | ClaudeAi\CRM\relax-kb-api\. Deploys relax-kb-api. Deploy-Portal-Function.ps1 does the same for relax-portal-api. |
| Setup-AI.ps1 | Stores the Anthropic key and AI limits on relax-kb-api. |
| Setup-Quotes.ps1 | Creates the Quotes mail app, the Relax-QuotesSenders group and its access policy. Add an agent: Add-DistributionGroupMember -Identity Relax-QuotesSenders -Member name@pnw.com |
| Setup-KB-Search.ps1 / Setup-KB-Storage.ps1 | Knowledge Base search index and storage. |
| Update-SecretExpiry.ps1 / Renew-AppSecret.ps1 | Record app-secret expiry dates (Orbit warns before they expire) and renew a secret. |
| Set-SitesSelected.ps1 | Gives a Function app access to the team site only (Sites.Selected). |
| Audit-CrmPermissions.ps1 | Lists the permissions consented for Relax-CRM. |
09Security Settings
Changes made in October 2026. Each script is in ClaudeAi\CRM\, shows what it will do and asks before changing anything.
- Legacy sign-in off: POP, IMAP and SMTP AUTH are disabled, the default authentication policy is Block Basic Auth, and mailbox plans keep them off for new mailboxes (Disable-LegacyAuth.ps1).
- No Anyone links: team-site sharing is New and existing guests (no Anyone links) and Copy link defaults to People in your organization, View (Set-NoAnyoneLinks.ps1; -Check shows, -Undo puts back). Existing Anyone links were removed Oct 10 (List-AnyoneLinks.ps1 / Remove-AnyoneLinks.ps1).
- Removing a person: Remove-UserAccess.ps1 removes a user from groups, mailbox permissions and admin roles and signs them out everywhere; -Undo restores from the JSON it saves.
- Guests: Remove-NedoGuests.ps1 shows guests before deleting; deleted users can be restored for 30 days.
- Exchange scripts: they check they are connected to the PNW tenant. Run them with -AdminUpn <your admin account>.
- Demo site: the demo account uses Demo_ lists, never sends client emails and is not in PNW-Team.