CRM Ticketing
WordPress plugin that lets customers submit and track support tickets synced with Dynamics 365 CRM, with an AI agent answering automatically.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/elmira-ashrafi/crm-manager/archive/refs/heads/main.zipCRM Ticketing — Dynamics 365 ↔ MuChat AI Integration for WordPress
A production-grade WordPress plugin that bridges a WordPress front-end with Microsoft Dynamics 365 CRM (Dataverse API v9.0), giving customers a self-service ticketing portal while an AI agent (MuChat) handles first-line responses. Built for high concurrency and reliability: ticket creation, file/voice attachments, and CRM/agent communication are dispatched as parallel async HTTP requests, and long-running work (bulk ticket sync, resolved-ticket archiving) runs in the background via Action Scheduler instead of blocking the request.
Originally built as a support-ticket integration for a live customer panel, so the plugin also ships a compatibility layer for the MihanPanel WordPress theme in addition to its own native routing.
✨ Key Features
- Dynamics 365 OAuth2 authentication — token-based auth against an ADFS/Azure AD endpoint, with separate credentials for the end-user and for the AI agent (chatbot) identity.
- Concurrent async HTTP with Guzzle — when a ticket update needs to hit both the CRM and the AI agent (and optionally upload an attachment) at the same time, requests are fired concurrently with
GuzzleHttp\Promiseand resolved withPromise\Utils::settle()instead of being chained sequentially, cutting response time under load. - Background job processing (Action Scheduler) — ticket creation triggers an
as_enqueue_async_action()job so the CRM sync and AI hand-off can complete after the response is already sent to the user; failed jobs are logged to dedicated files (background/*-failed.txt) instead of failing silently. - Custom routing layer — registers its own rewrite rules (
/my-tickets/,/my-tickets/new-ticket/,/my-tickets/ticket-{id}/) viaadd_rewrite_rule, independent of the CMS page structure. - MihanPanel compatibility shim — detects when the plugin is being rendered inside a MihanPanel tab and swaps in the right template/behavior without duplicating logic.
- Jalali (Persian) date handling — converts and formats all CRM timestamps to Jalali dates with human-readable relative time (e.g. "۲ روز قبل") via
morilog/jalali. - Attachment pipeline with real validation — audio/file uploads are checked against an explicit MIME allow-list using
finfo(actual content sniffing, not just file extension), with size limits enforced before anything reaches the CRM. - Transient-based caching — support-agent lookups are cached with WordPress transients to avoid re-querying Dataverse on every page load.
- Resolved-ticket archival job — a scheduled task walks Dataverse's
@odata.nextLinkpagination to pull newly resolved tickets and their full annotation thread into local conversation logs (conversations/*.txt), rotating files once a batch exceeds 5,000 tickets — useful as training/eval data for the AI agent. - Shutdown error logging — a
register_shutdown_functionhook captures fatal PHP errors with file/line context to a plugin-local log, even when WordPress's own error handling doesn't catch them. - CLI support — registers a
wp crmcommand when running under WP-CLI.
🧠 Architecture Overview
┌─────────────────────┐
│ Customer (Web UI) │
└──────────┬───────────┘
│ AJAX (jQuery)
▼
┌────────────────────────────────┐
│ WordPress Plugin (this repo) │
│ ┌──────────────────────────┐ │
│ │ CRMTicketManager (core) │ │
│ │ • OAuth2 token handling │ │
│ │ • routing / query vars │ │
│ ├──────────────────────────┤ │
│ │ ArchiveTicketsHandler │ │
│ │ SingleTicketsHandler │ │
│ │ • Guzzle async requests │ │
│ │ • file/voice validation │ │
│ └──────────────────────────┘ │
└─────┬──────────────────┬────────┘
│ │
(parallel, via Guzzle Promises)
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ Dynamics 365 CRM │ │ MuChat AI Agent │
│ (Dataverse API v9) │ │ (LLM endpoint) │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
└───────────┬────────────┘
▼
Response merged & written back to
CRM ticket + returned to customer
┌─────────────────────────────────────────────┐
│ Action Scheduler (background) │
│ • post-creation CRM/agent sync │
│ • daily resolved-ticket → conversation export │
└─────────────────────────────────────────────┘
📋 Requirements
- WordPress 6.x+
- PHP 8.1+
- Composer
- Microsoft Dynamics 365 CRM (Dataverse API v9.0)
- OAuth2 token endpoint (ADFS or Azure AD)
- MuChat Agent API credentials
Composer dependencies (see composer.json):
| Package | Version | Purpose |
|---|---|---|
guzzlehttp/guzzle |
^7.9 | Concurrent/async HTTP requests to CRM & AI agent |
woocommerce/action-scheduler |
^3.9 | Reliable background job processing |
morilog/jalali |
^3.4 | Gregorian → Jalali date conversion |
⚙️ Installation & Setup
-
Clone the plugin into
wp-content/plugins/and install dependencies:composer install --no-dev --optimize-autoloader -
Activate the plugin from the WordPress admin panel.
-
Add the following constants to
wp-config.php, before the line/* That's all, stop editing! Happy publishing. */:// CRM global data define('CRM_TOKEN_API_LINK', "https://sts.eversso.com/adfs/oauth2/token"); // token endpoint, almost always the same define('CRM_API_LINK', YOUR_ORGANIZATION_API_LINK); // e.g. https://your-organization-domain.com/api/data/v9.0 // Client CRM credentials define('CLIENT_ID', YOUR_CRM_CLIENT_ID); // from your ADFS server define('CLIENT_SECRET', YOUR_CRM_CLIENT_SECRET); // from your ADFS server define('CLIENT_USERNAME', YOUR_CRM_CLIENT_USERNAME); define('CLIENT_PASSWORD', YOUR_CRM_CLIENT_PASSWORD); define('CLIENT_UUID', YOUR_CRM_CLIENT_USER_UUID); // from .../api/data/v9.0/WhoAmI → UserId // MuChat chatbot identity (a dedicated CRM user acting on behalf of the AI agent) define('CHATBOT_USERNAME', CHATBOT_USERNAME); define('CHATBOT_PASSWORD', CHATBOT_PASSWORD); define('CHATBOT_UUID', CHATBOT_UUID); // from .../systemusers?$select=systemuserid&$filter=domainname eq 'CHATBOT_USERNAME' // MuChat agent credentials define('MUCHAT_TOKEN', MUCHAT_TOKEN_VALUE); define('AGENT_ID', AGENT_ID_VALUE); -
Flush permalinks once (the plugin does this automatically on activation/deactivation, but a manual visit to Settings → Permalinks → Save helps if custom routes 404).
🚀 How It Works
- The customer submits a new ticket or reply through the front-end UI (AJAX).
- The plugin authenticates with Dynamics CRM via OAuth2 and obtains an access token (cached per-request, one identity for the customer flow and one for the agent flow).
- For actions that need both systems — e.g. posting a reply and asking the AI agent for a response and uploading an attachment — the plugin fires the requests concurrently with Guzzle promises rather than one after another, and waits for all of them to settle.
- The MuChat LLM agent generates a response based on the ticket/conversation context.
- The plugin writes the agent's (or the customer's) message back into Dynamics CRM as an annotation, tagged with the correct identity (chatbot vs. customer).
- Heavier, non-blocking work — syncing a newly created ticket to the CRM/agent after the response is sent, and the daily export of resolved tickets — is handed off to Action Scheduler, so the customer-facing request stays fast.
- Newly resolved tickets and their full conversation thread are periodically pulled from Dataverse (paginated via
@odata.nextLink) and written to local conversation logs, batched and rotated at 5,000 tickets per file.
⚠️ Security Notice
This plugin requires CRM credentials and API tokens.
- Never commit
wp-config.phpto GitHub. - Never expose client secrets publicly.
- Prefer environment variables over hardcoded constants where possible.
- Restrict API permissions to the minimum required scope.
🔐 Recommended Security Improvements (for production)
- Use environment variables instead of hardcoded
wp-config.phpconstants. - Store secrets in server-level configuration (not in version control).
- Restrict CRM API scopes to only the entities/operations this plugin needs.
- Enable HTTPS only, and remove any
verify => false/sslverify => falseoverrides used during development. - Add structured logging & rate limiting around the CRM/agent endpoints.
Author
Elmira Ashrafi — github.com/elmira-ashrafi