424 lines
10 KiB
Markdown
424 lines
10 KiB
Markdown
|
|
# Ticket System - Email Integration
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The ticket system integrates with BMC Hub's email workflow engine to automatically create tickets from incoming emails. Uses **Option B: Message-ID Threading** for robust email-to-ticket linkage.
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
Incoming Email → Email Classifier → Email Workflow Engine → Ticket Integration
|
||
|
|
↓
|
||
|
|
create_ticket action
|
||
|
|
↓
|
||
|
|
EmailTicketIntegration.process_email_for_ticket()
|
||
|
|
↓
|
||
|
|
┌───────┴───────┐
|
||
|
|
↓ ↓
|
||
|
|
New Ticket Reply to Existing
|
||
|
|
(creates TKT-XXX) (links via Message-ID)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Email Threading Logic
|
||
|
|
|
||
|
|
### Message-ID Based Threading (Option B)
|
||
|
|
|
||
|
|
The system uses email headers to detect if an email is part of an existing ticket thread:
|
||
|
|
|
||
|
|
1. **In-Reply-To Header**: Check if contains `TKT-YYYYMMDD-XXX` pattern
|
||
|
|
2. **References Header**: Check all message IDs in chain for ticket number
|
||
|
|
3. **Subject Line**: Check for `[TKT-YYYYMMDD-XXX]` or `Re: TKT-YYYYMMDD-XXX`
|
||
|
|
|
||
|
|
If ticket number found → **Link to existing ticket**
|
||
|
|
If NOT found → **Create new ticket**
|
||
|
|
|
||
|
|
### Ticket Number Format
|
||
|
|
|
||
|
|
Pattern: `TKT-YYYYMMDD-XXX`
|
||
|
|
- `TKT`: Prefix
|
||
|
|
- `YYYYMMDD`: Date (e.g., 20251215)
|
||
|
|
- `XXX`: Sequential number (001-999)
|
||
|
|
|
||
|
|
Example: `TKT-20251215-001`
|
||
|
|
|
||
|
|
## Workflow Actions
|
||
|
|
|
||
|
|
### 1. `create_ticket`
|
||
|
|
|
||
|
|
Creates new ticket OR links to existing ticket (smart routing).
|
||
|
|
|
||
|
|
**Workflow Definition:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"name": "Support Request → Ticket",
|
||
|
|
"classification_trigger": "support_request",
|
||
|
|
"workflow_steps": [
|
||
|
|
{
|
||
|
|
"action": "create_ticket",
|
||
|
|
"params": {
|
||
|
|
"customer_id": 123,
|
||
|
|
"assigned_to_user_id": 5
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Parameters:**
|
||
|
|
- `customer_id` (optional): BMC customer ID to link ticket to
|
||
|
|
- `assigned_to_user_id` (optional): User ID to assign ticket to
|
||
|
|
- `priority` (optional): Override priority detection (low/normal/high/critical)
|
||
|
|
|
||
|
|
**Returns:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"ticket_id": 42,
|
||
|
|
"ticket_number": "TKT-20251215-001",
|
||
|
|
"created": true,
|
||
|
|
"linked": false
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. `link_email_to_ticket`
|
||
|
|
|
||
|
|
Explicitly link email to a specific ticket (manual routing).
|
||
|
|
|
||
|
|
**Workflow Definition:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"action": "link_email_to_ticket",
|
||
|
|
"params": {
|
||
|
|
"ticket_number": "TKT-20251215-001"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Parameters:**
|
||
|
|
- `ticket_number` (required): Target ticket number
|
||
|
|
|
||
|
|
**Returns:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"ticket_id": 42,
|
||
|
|
"ticket_number": "TKT-20251215-001",
|
||
|
|
"linked": true
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Automatic Features
|
||
|
|
|
||
|
|
### Tag Extraction
|
||
|
|
|
||
|
|
Extracts `#hashtags` from email body and adds as ticket tags:
|
||
|
|
|
||
|
|
```
|
||
|
|
Email body: "We need help with #billing #urgent issue"
|
||
|
|
→ Ticket tags: ["billing", "urgent"]
|
||
|
|
```
|
||
|
|
|
||
|
|
- Max 10 tags per ticket
|
||
|
|
- Minimum 3 characters
|
||
|
|
- Converted to lowercase
|
||
|
|
|
||
|
|
### Priority Detection
|
||
|
|
|
||
|
|
Automatically determines ticket priority from email content:
|
||
|
|
|
||
|
|
**Critical**: kritisk, critical, down, nede, urgent, akut
|
||
|
|
**High**: høj, high, vigtig, important, haster
|
||
|
|
**Low**: lav, low, spørgsmål, question, info
|
||
|
|
**Normal**: Everything else (default)
|
||
|
|
|
||
|
|
Checks both subject line and extracted tags.
|
||
|
|
|
||
|
|
### Email Metadata
|
||
|
|
|
||
|
|
Stores rich metadata in ticket:
|
||
|
|
|
||
|
|
- **Description**: Formatted with sender email, received date, original body
|
||
|
|
- **Custom Fields**: `email_from`, `email_message_id`, `created_from_email`
|
||
|
|
- **Email Log**: Full threading data in `tticket_email_log` table
|
||
|
|
|
||
|
|
## Database Storage
|
||
|
|
|
||
|
|
### `tticket_email_log` Table
|
||
|
|
|
||
|
|
Stores all email-ticket linkages for audit trail:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
CREATE TABLE tticket_email_log (
|
||
|
|
id SERIAL PRIMARY KEY,
|
||
|
|
ticket_id INTEGER NOT NULL REFERENCES tticket_tickets(id),
|
||
|
|
email_message_id TEXT NOT NULL,
|
||
|
|
email_subject TEXT,
|
||
|
|
email_from TEXT NOT NULL,
|
||
|
|
email_received_at TIMESTAMP,
|
||
|
|
is_reply BOOLEAN DEFAULT FALSE,
|
||
|
|
thread_data JSONB,
|
||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
**`thread_data` JSONB structure:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"message_id": "<abc123@example.com>",
|
||
|
|
"in_reply_to": "<TKT-20251215-001@bmcnetworks.dk>",
|
||
|
|
"references": "<ref1@example.com> <ref2@example.com>"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Usage Examples
|
||
|
|
|
||
|
|
### Example 1: Basic Support Request
|
||
|
|
|
||
|
|
**Incoming Email:**
|
||
|
|
```
|
||
|
|
From: customer@example.com
|
||
|
|
Subject: Help with network issue
|
||
|
|
Body: Our internet is down. Can you help?
|
||
|
|
```
|
||
|
|
|
||
|
|
**Workflow Configuration:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"name": "Support Email → Ticket",
|
||
|
|
"classification_trigger": "support_request",
|
||
|
|
"confidence_threshold": 0.7,
|
||
|
|
"workflow_steps": [
|
||
|
|
{
|
||
|
|
"action": "create_ticket",
|
||
|
|
"params": {
|
||
|
|
"assigned_to_user_id": 5
|
||
|
|
}
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Result:**
|
||
|
|
- Creates ticket `TKT-20251215-001`
|
||
|
|
- Priority: `normal` (no urgent keywords)
|
||
|
|
- Tags: [] (no hashtags found)
|
||
|
|
- Assigned to user 5
|
||
|
|
- Email logged in `tticket_email_log`
|
||
|
|
|
||
|
|
### Example 2: Email Reply to Ticket
|
||
|
|
|
||
|
|
**Incoming Email:**
|
||
|
|
```
|
||
|
|
From: customer@example.com
|
||
|
|
Subject: Re: TKT-20251215-001
|
||
|
|
In-Reply-To: <TKT-20251215-001@bmcnetworks.dk>
|
||
|
|
Body: Thanks, issue is resolved now
|
||
|
|
```
|
||
|
|
|
||
|
|
**Result:**
|
||
|
|
- **Detects existing ticket** via In-Reply-To header
|
||
|
|
- **Adds comment** to ticket `TKT-20251215-001`
|
||
|
|
- Does NOT create new ticket
|
||
|
|
- Logs as reply (`is_reply=true`)
|
||
|
|
|
||
|
|
### Example 3: Tagged Urgent Request
|
||
|
|
|
||
|
|
**Incoming Email:**
|
||
|
|
```
|
||
|
|
From: vip@example.com
|
||
|
|
Subject: URGENT: Server down!
|
||
|
|
Body: Production server is down #critical #server
|
||
|
|
```
|
||
|
|
|
||
|
|
**Result:**
|
||
|
|
- Creates ticket with priority `critical` (subject keyword)
|
||
|
|
- Tags: `["critical", "server"]`
|
||
|
|
- Custom field: `created_from_email=true`
|
||
|
|
|
||
|
|
## API Endpoints
|
||
|
|
|
||
|
|
### Get Ticket Email Thread
|
||
|
|
|
||
|
|
```http
|
||
|
|
GET /api/v1/tickets/{ticket_id}/emails
|
||
|
|
```
|
||
|
|
|
||
|
|
Returns chronological list of all emails linked to ticket.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"ticket_id": 42,
|
||
|
|
"ticket_number": "TKT-20251215-001",
|
||
|
|
"emails": [
|
||
|
|
{
|
||
|
|
"id": 1,
|
||
|
|
"email_message_id": "<abc123@example.com>",
|
||
|
|
"email_from": "customer@example.com",
|
||
|
|
"email_subject": "Help with issue",
|
||
|
|
"email_received_at": "2025-12-15T10:00:00Z",
|
||
|
|
"is_reply": false,
|
||
|
|
"created_at": "2025-12-15T10:01:00Z"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": 2,
|
||
|
|
"email_message_id": "<def456@example.com>",
|
||
|
|
"email_from": "customer@example.com",
|
||
|
|
"email_subject": "Re: TKT-20251215-001",
|
||
|
|
"email_received_at": "2025-12-15T11:00:00Z",
|
||
|
|
"is_reply": true,
|
||
|
|
"created_at": "2025-12-15T11:01:00Z"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Find Tickets by Email Address
|
||
|
|
|
||
|
|
```http
|
||
|
|
GET /api/v1/tickets/by-email/{email_address}
|
||
|
|
```
|
||
|
|
|
||
|
|
Returns all tickets associated with an email address.
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"email_address": "customer@example.com",
|
||
|
|
"tickets": [
|
||
|
|
{
|
||
|
|
"id": 42,
|
||
|
|
"ticket_number": "TKT-20251215-001",
|
||
|
|
"subject": "Network issue",
|
||
|
|
"status": "open",
|
||
|
|
"created_at": "2025-12-15T10:00:00Z"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Settings (`.env`)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Ticket system enabled
|
||
|
|
TICKET_ENABLED=true
|
||
|
|
|
||
|
|
# Email integration enabled (requires email system)
|
||
|
|
TICKET_EMAIL_INTEGRATION=true
|
||
|
|
|
||
|
|
# Auto-assign new tickets (requires user_id)
|
||
|
|
TICKET_AUTO_ASSIGN=false
|
||
|
|
TICKET_DEFAULT_ASSIGNEE_ID=5
|
||
|
|
|
||
|
|
# Default priority for new tickets
|
||
|
|
TICKET_DEFAULT_PRIORITY=normal
|
||
|
|
```
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
### Test Email-to-Ticket Creation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://localhost:8001/api/v1/test/email-to-ticket \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"email_data": {
|
||
|
|
"message_id": "<test123@example.com>",
|
||
|
|
"subject": "Test ticket",
|
||
|
|
"from_address": "test@example.com",
|
||
|
|
"body": "This is a test #testing",
|
||
|
|
"received_at": "2025-12-15T10:00:00Z"
|
||
|
|
}
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
### Test Email Reply Linking
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -X POST http://localhost:8001/api/v1/test/email-to-ticket \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-d '{
|
||
|
|
"email_data": {
|
||
|
|
"message_id": "<reply456@example.com>",
|
||
|
|
"subject": "Re: TKT-20251215-001",
|
||
|
|
"from_address": "test@example.com",
|
||
|
|
"body": "Reply to existing ticket",
|
||
|
|
"received_at": "2025-12-15T11:00:00Z",
|
||
|
|
"in_reply_to": "<TKT-20251215-001@bmcnetworks.dk>"
|
||
|
|
}
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Tickets Not Created
|
||
|
|
|
||
|
|
**Check:**
|
||
|
|
1. Email workflow engine enabled (`EMAIL_WORKFLOWS_ENABLED=true`)
|
||
|
|
2. Workflow exists for classification trigger
|
||
|
|
3. Confidence threshold met
|
||
|
|
4. Workflow action is `create_ticket` (NOT old `create_ticket` stub)
|
||
|
|
|
||
|
|
**Debug:**
|
||
|
|
```sql
|
||
|
|
-- Check workflow executions
|
||
|
|
SELECT * FROM email_workflow_executions
|
||
|
|
WHERE status = 'failed'
|
||
|
|
ORDER BY created_at DESC
|
||
|
|
LIMIT 10;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Email Not Linked to Ticket
|
||
|
|
|
||
|
|
**Check:**
|
||
|
|
1. Ticket number format correct (`TKT-YYYYMMDD-XXX`)
|
||
|
|
2. Ticket exists in database
|
||
|
|
3. Email headers contain ticket number (In-Reply-To, References, Subject)
|
||
|
|
|
||
|
|
**Debug:**
|
||
|
|
```sql
|
||
|
|
-- Check email logs
|
||
|
|
SELECT * FROM tticket_email_log
|
||
|
|
WHERE ticket_id = 42
|
||
|
|
ORDER BY created_at DESC;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Duplicate Tickets Created
|
||
|
|
|
||
|
|
**Check:**
|
||
|
|
1. Email reply headers missing ticket number
|
||
|
|
2. Subject line doesn't match pattern (e.g., `Re: Ticket 123` instead of `Re: TKT-20251215-001`)
|
||
|
|
|
||
|
|
**Solution:**
|
||
|
|
- Ensure outgoing ticket emails include ticket number in subject: `[TKT-20251215-001]`
|
||
|
|
- Add ticket number to Message-ID: `<TKT-20251215-001-reply@bmcnetworks.dk>`
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
1. **Include Ticket Number in Replies**: Always include `[TKT-YYYYMMDD-XXX]` in subject line
|
||
|
|
2. **Use Message-ID with Ticket Number**: Format: `<TKT-YYYYMMDD-XXX@bmcnetworks.dk>`
|
||
|
|
3. **Set Customer ID in Workflow**: Improves ticket organization and reporting
|
||
|
|
4. **Monitor Workflow Executions**: Check `email_workflow_executions` table regularly
|
||
|
|
5. **Review Failed Actions**: Alert on repeated workflow failures
|
||
|
|
|
||
|
|
## Security Considerations
|
||
|
|
|
||
|
|
1. **No Email Body Storage**: Only stores metadata and body in ticket description
|
||
|
|
2. **Sender Validation**: Consider implementing sender verification (SPF/DKIM)
|
||
|
|
3. **Spam Prevention**: Email classifier should filter spam before workflow execution
|
||
|
|
4. **Customer Isolation**: Ensure `customer_id` properly set to prevent data leakage
|
||
|
|
|
||
|
|
## Future Enhancements
|
||
|
|
|
||
|
|
- **Attachment Handling**: Link email attachments to ticket attachments
|
||
|
|
- **Email Templates**: Auto-reply with ticket number
|
||
|
|
- **SLA Integration**: Start SLA timer on ticket creation from email
|
||
|
|
- **Multi-Ticket Threading**: Support one email creating multiple tickets
|
||
|
|
- **Smart Customer Detection**: Auto-detect customer from sender domain
|
||
|
|
|
||
|
|
## Related Documentation
|
||
|
|
|
||
|
|
- [Ticket System Architecture](TICKET_SYSTEM_ARCHITECTURE.md)
|
||
|
|
- [Email Workflow System](EMAIL_WORKFLOWS.md)
|
||
|
|
- [Database Schema](../migrations/025_ticket_module.sql)
|