CRM Ticketing
WordPress plugin that lets customers submit and track support tickets synced with Dynamics 365 CRM, with an AI agent answering automatically.
by ata ashrafi · github.com/elmira-ashrafi/crm-manager · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/elmira-ashrafi/crm-manager/archive/refs/heads/main.zipReadme
CRM 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