# GoodBooks EIP — Enterprise Integration & Conversational Automation Platform

**Document type:** Product overview, functional specification, technical reference  
**Audience:** End users · Implementation team · Administrators · Developers  
**Product:** GoodBooks GB5 — EIP (Enterprise Integration Platform)  
**Version:** 1.0

---

## Table of Contents

1. [What Is EIP?](#1-what-is-eip)
2. [Why EIP Exists — The Problem It Solves](#2-why-eip-exists)
3. [Vision and Goals](#3-vision-and-goals)
4. [What EIP Can Do — Feature Overview](#4-what-eip-can-do)
5. [Supported Channels](#5-supported-channels)
6. [The Two Interaction Modes](#6-the-two-interaction-modes)
7. [How a Conversation Works — End User Perspective](#7-how-a-conversation-works)
8. [Flows — The Heart of EIP](#8-flows)
9. [Step Types — Building Blocks of a Flow](#9-step-types)
10. [Context Variables — Memory Inside a Flow](#10-context-variables)
11. [Session Management — Multi-Turn Conversations](#11-session-management)
12. [Direct Action — Generalized One-Click Actions](#12-direct-action)
    - 12a. [How Direct Actions Work — End to End](#12a-how-direct-actions-work)
    - 12b. [Workflow Task Assignment Notifications](#12b-workflow-task-notifications)
13. [WhatsApp Integration Deep Dive](#13-whatsapp-integration)
14. [Capabilities and Actions](#14-capabilities-and-actions)
15. [The Six-Phase Execution Engine](#15-the-six-phase-execution-engine)
16. [Channel Handlers — How Responses Are Delivered](#16-channel-handlers)
17. [Routing Rules — Directing Traffic to the Right Flow](#17-routing-rules)
18. [Security and Multi-Tenancy](#18-security-and-multi-tenancy)
19. [For Administrators — Configuration Reference](#19-for-administrators)
20. [For Implementation Teams — Setting Up a Flow](#20-for-implementation-teams)
21. [For Developers — Architecture and Integration](#21-for-developers)
22. [Database Tables Reference](#22-database-tables-reference)
23. [Glossary](#23-glossary)

---

## 1. What Is EIP?

EIP (Enterprise Integration Platform) is GoodBooks GB5's conversational automation engine. It lets employees, managers, and external users interact with the GoodBooks ERP system using the **messaging tools they already use every day** — WhatsApp, Microsoft Teams, Slack, Telegram, or SMS — instead of logging into a web application.

EIP handles two fundamentally different types of interaction:

**Conversational transactions** — A user sends a message and EIP guides them through a structured dialogue to complete a business task. For example, an employee types "apply leave" on WhatsApp and EIP asks them for the from date, to date, and reason, then submits the leave application on their behalf — no browser required.

**Automated notifications with actions** — EIP sends workflow-triggered messages (approvals, alerts, status updates) to users and lets them respond with a button click or a short reply, which immediately updates a business record. A manager receives an approval request email with "Approve" and "Reject" links; clicking either completes the action instantly.

EIP is not a chatbot in the casual sense. It is a structured, rule-driven platform where every interaction follows a predefined flow designed by the implementation team to exactly match the organisation's business process.

---

## 2. Why EIP Exists — The Problem It Solves

Traditional ERP systems require users to be at a computer, logged into a web application, and navigating to the right module before they can complete even a simple task like approving a leave request or updating a call status.

This creates real friction:

- A field sales executive on the road cannot approve a quotation without stopping to open a laptop.
- A factory supervisor receives an SMS notification about a pending approval but must log into the ERP to act on it.
- A manager is sent a workflow email but the "click here to approve" link takes them to a login page, then through several screens to reach the actual approval button.
- HR managers waste time on follow-up calls because employees forget to submit timesheets or update attendance.

EIP removes every one of these barriers. Users complete ERP transactions from inside messaging apps they check dozens of times a day, on any device, often in under thirty seconds.

---

## 3. Vision and Goals

**Core vision:** Make every GoodBooks business process accessible and completable through any messaging channel, without requiring users to log into the web application.

**Goals:**

| Goal | What it means in practice |
|------|--------------------------|
| Channel agnostic | The same business flow runs on WhatsApp, Teams, Slack, or SMS without code changes |
| Zero-friction approvals | Workflow approvals completable in one click from an email or one-tap from a message |
| Structured dialogue | Guided, multi-step conversations that collect data and execute transactions accurately |
| ERP integration | Native connection to all GoodBooks modules — leave, payroll, CRM, procurement, workflow |
| Multi-tenant | Each organisation (tenant) has its own flows, routing rules, and configuration |
| Audit trail | Every interaction is logged — who did what, when, from which channel |
| Secure | Signed tokens prevent replay attacks; tenant isolation enforced at every data access |

---

## 4. What EIP Can Do — Feature Overview

### 4.1 Workflow Approvals via Email

When a workflow task is generated (leave approval, purchase order approval, etc.), EIP can embed one-click approval links directly in the notification email:

- **Approve**, **Reject**, and **Return** buttons in the email body
- Clicking a button opens a browser page that immediately processes the approval — no login required
- Each link is cryptographically signed and expires after 48 hours
- Each link is one-time-use — a second click shows "Already Actioned"
- Full audit record of every link use stored in the database

### 4.2 WhatsApp Workflow Approvals

The same approval actions are available as WhatsApp interactive buttons. A manager receives a WhatsApp message with the approval details and taps Approve or Reject to complete the workflow action.

### 4.3 Conversational Transactions via Chat

Users can complete ERP transactions entirely through natural-language dialogue on any supported channel:

- Apply for leave (collect from date, to date, reason → submit)
- Check leave balance (query → display)
- Update call status in CRM (collect call ID, new status → update)
- Submit expense claims
- Query payslip details
- Any other business process defined as a flow

### 4.4 Interactive Menus

Flows can present users with a numbered or button menu (e.g., "1. Check Leave Balance  2. Apply Leave  3. View Payslip") and route the conversation to the appropriate sub-flow based on the selection.

### 4.5 External API Integration

Flows can call internal GB5 service endpoints and external APIs mid-conversation, store the result in context, and use it in subsequent messages. For example: fetch a user's leave balance from the HR module and display it in the reply.

### 4.6 Notifications with Actions

EIP can push proactive messages to users (alerts, reminders, status changes) through any channel, with or without interactive response buttons.

### 4.7 OTP Verification

Flows can include an OTP step that sends a one-time password to the user and verifies their response before proceeding to a sensitive action.

---

## 5. Supported Channels

EIP currently supports six delivery channels. Each channel can both receive messages from users and send messages back.

| Channel | Use case | Notes |
|---------|----------|-------|
| **WhatsApp** | Field employees, approvals, alerts | Supports text, media, template messages, and interactive buttons. Dual-provider: Celitix and Meta (Facebook Graph API) |
| **Microsoft Teams** | Office employees, approvals | Sends Adaptive Cards with action buttons. Reads webhook URL from configuration |
| **Slack** | Developer teams, alerts | Sends Block Kit messages via Slack incoming webhooks |
| **Telegram** | General messaging | Bot API integration with HTML formatting |
| **SMS** | Field workers, simple alerts | Plain text only, maximum 160 characters |
| **Postman (testing)** | Development and testing | Simulates delivery; logs the full payload and returns a fake success. No actual message sent |

Adding a new channel requires implementing a single interface (`IEIPChannelHandler`) and registering it — no changes to the flow engine or business logic.

---

## 6. The Two Interaction Modes

EIP processes incoming messages in one of two modes depending on the source.

### Mode 1 — Button/Template Response (Approval Mode)

A user taps an action button on a structured message (WhatsApp template, Teams Adaptive Card). The button payload carries the action identifier, template reference, and the business record ID.

**Button payload format:** `ACTION_TemplateId_ObjectId`

Example: `ACCEPT_1042_8800` means "Accept action on TemplateId 1042 for ObjectId 8800."

EIP decodes the payload, identifies the approval action (Accept / Reject / Forward), resolves the corresponding flow code from the mail template configuration, and executes the approval via the Template Approval business logic layer.

**Key behaviours in Mode 1:**
- No conversational back-and-forth — one button press = one business action
- The flow code is resolved from the database (MMAILTEMPLATE.FLOWCODE) not hardcoded
- The tenant is determined from the authenticated session, not from the payload
- Full audit trail via TemplateApprovalBLL

### Mode 2 — Conversational (Chat Mode)

A user sends a free-text message. EIP routes it to the appropriate conversational flow based on routing rules, restores any in-progress session, and continues the multi-turn dialogue.

**Key behaviours in Mode 2:**
- Routing rules match the incoming message to a flow (exact, starts-with, contains, or regex matching)
- Session state is preserved between messages — the user can stop mid-flow and resume
- The flow engine executes step by step, collecting data, branching on conditions, calling APIs, and finally submitting the transaction
- A session expires after 30 minutes of inactivity

---

## 7. How a Conversation Works — End User Perspective

This section describes EIP from the perspective of an employee using WhatsApp.

### Example: Applying for Leave

**User:** "apply leave"  
**EIP:** "What is your leave start date? (DD-MM-YYYY)"  
**User:** "15-05-2026"  
**EIP:** "What is your leave end date? (DD-MM-YYYY)"  
**User:** "18-05-2026"  
**EIP:** "Please select leave type: 1. Annual Leave  2. Sick Leave  3. Casual Leave"  
**User:** "1"  
**EIP:** "Any remarks? (Type 'skip' to leave blank)"  
**User:** "Family vacation"  
**EIP:** "Your leave request from 15-May-2026 to 18-May-2026 (Annual Leave) has been submitted. Your application number is LVE-2026-0431."

The employee never opened a browser. The ERP module processed the request exactly as if it were submitted from the web interface.

### Example: Workflow Approval via Email

A manager receives an email:

> **Purchase Order Approval Required**  
> Vendor: ABC Supplies Pvt Ltd  
> Amount: ₹1,45,000  
> Requested by: Rajeev Kumar  
>  
> [Approve] [Reject] [Return for Revision]

The manager clicks **Approve**. A browser page opens:

> **Action Successful**  
> Task #PO-2026-0091 has been Approved successfully.  
> GoodBooks ERP — Workflow Approval

The purchase order is approved. The manager never logged in. The action is recorded with a timestamp and audit trail.

### What Happens if Something Goes Wrong?

| Situation | What the user sees |
|-----------|-------------------|
| Invalid input (wrong date format) | "Please enter a valid date in DD-MM-YYYY format." |
| Session expired (> 30 min idle) | New message starts a fresh flow from the beginning |
| Approval link expired (> 48 hours) | "This approval link has expired or is not valid." |
| Approval link already used | "This link was already used on 2026-05-15 09:23 UTC." |
| Revoked link | "This link has been revoked." |
| Service error | Friendly error message; internal exception logged for admin review |

---

## 8. Flows — The Heart of EIP

A **flow** is the script of a conversation. It defines exactly what questions to ask, in what order, what to do with the answers, and how to branch based on conditions.

Flows are stored in the database table `MEIPFLOWDEFINITION`. Each flow has:

- A unique **FlowCode** (e.g., `LEAVE_REQUEST_FLOW`, `HR_MENU_FLOW`)
- A **StartStepCode** — the first step to execute
- A dictionary of **Steps** — each step is a named node in the flow

### Flow Definition Format

```json
{
  "FlowCode": "LEAVE_REQUEST_FLOW",
  "StartStepCode": "ASK_FROM",
  "Steps": {
    "ASK_FROM": {
      "StepType": "INPUT",
      "MessageTemplate": "What is your leave start date? (DD-MM-YYYY)",
      "ContextKey": "LeaveFrom",
      "Validation": {
        "Required": true,
        "Regex": "^\\d{2}-\\d{2}-\\d{4}$",
        "ErrorMessage": "Please use DD-MM-YYYY format."
      },
      "NextStepCode": "ASK_TO"
    },
    "ASK_TO": {
      "StepType": "INPUT",
      "MessageTemplate": "What is your leave end date?",
      "ContextKey": "LeaveTo",
      "Validation": { "Required": true },
      "NextStepCode": "SUBMIT"
    },
    "SUBMIT": {
      "StepType": "CAPABILITY",
      "CapabilityCode": "CRUD_OPERATION",
      "ActionType": "POST",
      "ApiEndpoint": "/Leave/SaveLeave",
      "MessageTemplate": "Your leave from {LeaveFrom} to {LeaveTo} has been submitted.",
      "NextStepCode": "END"
    },
    "END": {
      "StepType": "END"
    }
  }
}
```

### Flow Variants

Flows can be:

- **Global** — applies to all tenants (stored with TenantId = -1)
- **Tenant-specific** — overrides the global flow for a particular organisation

When loading a flow, EIP always checks for a tenant-specific version first and falls back to the global definition.

---

## 9. Step Types — Building Blocks of a Flow

Every step in a flow has a `StepType` that controls its behaviour.

### MESSAGE

Sends a message to the user and moves immediately to the next step. No user input expected.

```json
{
  "StepType": "MESSAGE",
  "MessageTemplate": "Welcome to GoodBooks HR self-service. How can I help you today?",
  "NextStepCode": "MAIN_MENU"
}
```

**Context variable substitution:** Any `{ContextKey}` token in `MessageTemplate` is replaced with the stored value. Example: `"Your balance is {LeaveBalance} days."` becomes `"Your balance is 12 days."` when `LeaveBalance = "12"` is in context.

---

### INPUT

Asks the user a question and stores their answer. Execution pauses and waits for the user's next message.

```json
{
  "StepType": "INPUT",
  "MessageTemplate": "Enter your employee ID:",
  "ContextKey": "EmployeeId",
  "Validation": {
    "Required": true,
    "Regex": "^EMP\\d{5}$",
    "ErrorMessage": "Please enter a valid employee ID (e.g., EMP00123)."
  },
  "NextStepCode": "FETCH_DETAILS"
}
```

**Validation options:**
- `Required: true` — rejects blank input
- `Regex` — validates against a regular expression pattern
- `ErrorMessage` — shown when validation fails; the step repeats

The collected value is stored in context under `ContextKey` and available to all subsequent steps.

---

### CHOICE

Presents a menu of options and routes based on the user's selection. On first visit, the menu is sent. Execution pauses. When the user replies with their selection, the flow routes to the matching step.

```json
{
  "StepType": "CHOICE",
  "MessageTemplate": "What would you like to do?",
  "Choices": {
    "Check Leave Balance": "LEAVE_BALANCE",
    "Apply Leave": "LEAVE_APPLY",
    "View Payslip": "PAYSLIP"
  }
}
```

If the user's reply does not match any option, the menu is re-sent with a "Invalid choice. Please try again." prefix.

---

### CONDITION / CONDITIONAL

Evaluates a context variable and routes to different steps based on the result. Supports multiple operators.

```json
{
  "StepType": "CONDITIONAL",
  "ConditionKey": "LeaveBalance",
  "Conditions": [
    { "Operator": "EXISTS", "NextStepCode": "SHOW_BALANCE" },
    { "Operator": "EQUALS", "Value": "0", "NextStepCode": "NO_BALANCE" }
  ],
  "DefaultNextStepCode": "FETCH_BALANCE"
}
```

**Supported operators:**

| Operator | What it checks |
|----------|---------------|
| `EXISTS` | Context value is not null/empty |
| `NOT_EXISTS` | Context value is null/empty |
| `EQUALS` | Value matches exactly (case-insensitive) |
| `NOT_EQUALS` | Value does not match |
| `IN` | Value is in a provided list |
| `GT` | Value is greater than (numeric) |
| `LT` | Value is less than (numeric) |

---

### ACTION

Executes an operation mid-flow: calls an API, triggers a MessageHub notification, or updates a database record. Does not pause for user input.

```json
{
  "StepType": "ACTION",
  "ActionType": "CALL_API",
  "ApiConfig": {
    "Url": "/Leave/GetBalance?UserId={UserId}",
    "Method": "GET"
  },
  "ContextKey": "LeaveBalance",
  "NextStepCode": "SHOW_BALANCE"
}
```

**ActionType options:**

| ActionType | What it does |
|------------|-------------|
| `CALL_API` | Calls an internal GB5 service endpoint. Result stored in `ContextKey` |
| `TRIGGER_MESSAGEHUB` | Triggers a MessageHub notification for a business entity (EntityId) |
| `DB_UPDATE` | Delegates to the Capability Engine with a configured CapabilityCode |

---

### CAPABILITY

Executes a registered business capability — a higher-level operation like submitting a leave request, validating an OTP, or running a risk check.

```json
{
  "StepType": "CAPABILITY",
  "CapabilityCode": "CRUD_OPERATION",
  "NextStepCode": "SUCCESS_MESSAGE"
}
```

The capability engine dispatches to the matching registered handler by `CapabilityCode`. The `CRUD_OPERATION` capability is the most commonly used — it calls any internal GB5 endpoint using parameters stored in the flow context.

---

### EXTERNAL_ACTION

Handles structured button responses in Mode 1. Typically used in template-based flows where users tap pre-defined action buttons rather than typing.

---

### END

Marks the end of the flow. When the engine reaches this step, the user session is expired, and no further messages are expected in this conversation.

```json
{
  "StepType": "END"
}
```

---

## 10. Context Variables — Memory Inside a Flow

Every EIP conversation has a **context** — a dictionary of key-value pairs that persists for the lifetime of the session.

Context variables are populated by:
- **INPUT steps** — the user's typed answer stored under `ContextKey`
- **ACTION steps** — API responses stored under `ContextKey`
- **CHOICE steps** — the selected option stored (if configured)
- **System values** — `UserId`, `TenantId`, `UserIdentifier` pre-populated at session start

Context variables are used by:
- **MESSAGE steps** — `{ContextKey}` tokens in `MessageTemplate` are replaced with stored values
- **ACTION steps** — URL parameters like `?UserId={UserId}` are substituted before the call
- **CONDITIONAL steps** — values evaluated against operators
- **CAPABILITY steps** — body parameters for API calls built from context

**Example:** A flow that first collects `{EmployeeId}` in an INPUT step, then uses it to fetch data: `"/Employee/GetDetails?Id={EmployeeId}"` — the `{EmployeeId}` is automatically replaced with the collected value before the HTTP call is made.

---

## 11. Session Management — Multi-Turn Conversations

A **session** represents the in-progress state of a conversation between one user and one flow. EIP maintains sessions to enable multi-turn conversations — a user can send messages at different times and EIP remembers exactly where they left off.

### Session Lifecycle

```
User sends first message
         |
         v
EIP matches routing rule, identifies FlowCode
         |
         v
New session created (TEIUSERSESSION)
  - UserIdentifier (phone number / Teams ID / etc.)
  - TenantId
  - ChannelType
  - FlowCode
  - CurrentStepCode = first step
  - ContextJson = {}
         |
         v
Each subsequent message from same user
         |
         v
EIP loads active session, restores CurrentStepCode + Context
         |
         v
Flow resumes from where it paused
         |
         v
After each INPUT/CHOICE step: session updated with new step + context
         |
         v
END step reached: session marked expired (STATUS=0)
```

### Session Expiry

A session expires automatically if the user is idle for **30 minutes**. If the user sends a message after their session has expired, EIP starts a fresh conversation from the beginning of the flow.

### Session Identification

A session is uniquely identified by the combination of:
- `UserIdentifier` — the user's platform identity (WhatsApp phone number, Teams user ID, etc.)
- `TenantId` — the organisation
- `ChannelType` — the platform (WhatsApp, Teams, Slack, etc.)

This means the same user can have simultaneous active sessions on different channels (e.g., a WhatsApp session and a Slack session) that do not interfere with each other.

---

## 12. Direct Action — Generalized One-Click Actions

EIP provides a generalized framework for embedding **click-to-act links** in email notifications and **interactive buttons** in WhatsApp/Teams messages. Any GB5 business action (workflow approval, service call acceptance, leave recommendation, etc.) can be linked to a button or email link without writing application code — the connection is configured entirely in the database.

The old workflow-specific email approval system (`IncludeApprovalActions`, `WorkflowTaskId`) has been replaced by this generalized framework.

---

### 12a. How Direct Actions Work — End to End

#### Step 1: Configure an Action Group (`MDIRECTACTION`)

An **action group** defines a set of related buttons for one business scenario. Each group has:

- `CONTEXTIDFIELD` — the JSON field in the entity payload that holds the record PK (e.g., `WorkflowTaskId`)
- `ASSIGNEEUSERIDFIELD` — the JSON field for the assignee's user ID (used in the audit trail and token binding)

Example (pre-seeded for workflow approvals):

```sql
INSERT MDIRECTACTION (DIRECTACTIONID, DIRECTACTIONCODE, DIRECTACTIONNAME,
  CONTEXTIDFIELD, ASSIGNEEUSERIDFIELD, CREATEDBYID, MODIFIEDBYID, SOURCETYPE, TENANTID)
VALUES (1, 'WFAPPROVAL', 'Workflow Approval', 'WorkflowTaskId', 'AssigneeUserId', -1, -1, 1, -1);
```

#### Step 2: Configure the Buttons (`MDIRECTACTIONDETAIL`)

Each row in `MDIRECTACTIONDETAIL` is one button within the group:

| Column | Purpose |
|--------|---------|
| `ACTIONCODE` | Unique code embedded in the signed token — identifies exactly which API to call |
| `ACTIONLABEL` | Button text shown on WhatsApp/Teams or link text in email |
| `EMAILPLACEHOLDER` | `##APPROVE_URL##` — the placeholder in the email template body that gets replaced |
| `BUTTONPAYLOADPREFIX` | `WF_APPROVE` — prefixed to ContextId to form the WhatsApp button payload (`WF_APPROVE_5001`) |
| `APIENDPOINT` | Internal GB5 endpoint the action calls (e.g., `/WorkFlow/WorkFlowActions`) |
| `HTTPMETHOD` | POST / PUT / PATCH / DELETE |
| `PAYLOADTEMPLATE` | JSON body template — `{ContextId}` is substituted at runtime |
| `NEEDSINPUT` | 0 = direct API call; 1 = start EIP conversational flow (collect reason, etc.) |
| `FLOWID` | FK to `MEIPFLOWDEFINITION` when `NEEDSINPUT=1`; -1 otherwise |

Pre-seeded workflow approval buttons:

```sql
INSERT MDIRECTACTIONDETAIL VALUES
  (1, 1, 1, 'WORKFLOW_APPROVE', 'Approve', 'APPROVE_URL', 'WF_APPROVE',
   '/WorkFlow/WorkFlowActions', 'POST', '{"Items":[{"TaskId":{ContextId},"Action":1}]}', 0, -1),
  (2, 1, 2, 'WORKFLOW_REJECT',  'Reject',  'REJECT_URL',  'WF_REJECT',
   '/WorkFlow/WorkFlowActions', 'POST', '{"Items":[{"TaskId":{ContextId},"Action":2}]}', 0, -1),
  (3, 1, 3, 'WORKFLOW_RETURN',  'Return',  'RETURN_URL',  'WF_RETURN',
   '/WorkFlow/WorkFlowActions', 'POST', '{"Items":[{"TaskId":{ContextId},"Action":3}]}', 0, -1);
```

#### Step 3: Link the Action Group to a Notification (`MACTION`)

Set `MACTION.DIRECTACTIONID` to the `MDIRECTACTION.DIRECTACTIONID` of the group you want to embed.
`-1` (the default) means no buttons — one-way notification only.

```sql
UPDATE MACTION SET DIRECTACTIONID = 1 WHERE ACTIONID = <your workflow approval action id>;
```

#### Step 4: Email — Signed Token Links

When the ActionProcessor's `EmailActionHandler` sends an email and `DirectActionId > -1`:

1. Loads all `MDIRECTACTIONDETAIL` rows for the group.
2. For each row that has a non-empty `EMAILPLACEHOLDER`, generates a **signed token**:

```
payload  = "{actionCode}:{contextId}:{tenantId}:{assigneeUserId}:{expiryTicks}"
token    = Base64Url(payload) + "." + Base64Url(HMAC-SHA256(payload, secret))
url      = "{ServiceBaseUrl}/Action/Execute?token={token}"
```

3. Replaces `##EMAILPLACEHOLDER##` in the email body with the full URL.

The email template must contain the placeholders exactly:

```html
<a href="##APPROVE_URL##">Approve</a>
<a href="##REJECT_URL##">Reject</a>
<a href="##RETURN_URL##">Return for Revision</a>
```

#### Step 5: WhatsApp / Teams — Interactive Buttons

When `MessageHubGeneratorEngine` sends a WhatsApp or Teams message and `MessageHubDTO.DirectActionId > -1`:

1. Loads all `MDIRECTACTIONDETAIL` rows for the group.
2. For each row with a non-empty `BUTTONPAYLOADPREFIX`, creates a `TemplateButtonDTO`:
   - `Id` = `{BUTTONPAYLOADPREFIX}_{ContextId}` (e.g., `WF_APPROVE_5001`)
   - `Title` = `ACTIONLABEL` (e.g., `Approve`)
3. Appends buttons to `MessageHubDTO.Buttons`.

The platform sends these as WhatsApp interactive quick-reply buttons or Teams Adaptive Card actions.

#### Step 6: Approver Clicks Email Link

```
GET /Action/Execute?token=<signed_token>
AllowAnonymous — no login required
```

1. Token is validated (HMAC signature + expiry).
2. SHA-256 hash checked against `TDIRECTACTIONTOKEN` — if already used, returns "Already Actioned" HTML.
3. Token use recorded in `TDIRECTACTIONTOKEN`.
4. `GenericApiDirectActionHandler` is invoked:
   - Loads `MDIRECTACTIONDETAIL` row by `actionCode`.
   - If `NEEDSINPUT=0`: substitutes `{ContextId}` into `PAYLOADTEMPLATE`, POSTs to `APIENDPOINT` with the Login header.
   - If `NEEDSINPUT=1`: reserved for EIP conversational flow launch (future).
5. Returns a styled HTML confirmation page.

#### Step 7: Approver Taps WhatsApp Button

Button payload arrives at webhook (e.g., `WF_APPROVE_5001`).

`MessageHubWebhookBLL` processing:

1. **Primary path** — check if payload matches `ACCEPT_`, `REJECT_`, `FORWARD_`, `APPROVE_`, `RETURN_` prefixes → routes to `TemplateApprovalBLL` (legacy template approval).
2. **Fallback path** — if no match, loads all active `MDIRECTACTIONDETAIL` rows and searches by `BUTTONPAYLOADPREFIX`:

```csharp
var matched = allDetails.FirstOrDefault(d =>
    payload.StartsWith(d.ButtonPayloadPrefix + "_", StringComparison.OrdinalIgnoreCase));

var contextId = int.Parse(payload[(matched.ButtonPayloadPrefix.Length + 1)..]);
await _handler.ExecuteAsync(matched.ActionCode, contextId, loginDTO.UserId, loginDTO, ct);
```

#### Token Security

| Threat | Protection |
|--------|-----------|
| Token forgery | HMAC-SHA256 with secret from HashiCorp Vault |
| Replay attack | SHA-256 hash stored in `TDIRECTACTIONTOKEN` on first use; second use rejected |
| Token expiry bypass | `expiresAt` ticks encoded in payload; validated before any action |
| Timing attack | `CryptographicOperations.FixedTimeEquals` constant-time comparison |
| Identity binding | `assigneeUserId` encoded in token — audit trail records correct actor |
| Resend safety | Old un-clicked tokens remain valid until 48h expiry; one-time-use check prevents double execution |

---

### 12b. Workflow Task Assignment Notifications

When a workflow instance is started (any entity — leave, PO, task, etc.), the workflow engine now creates TOUTBOX events for each task assigned at the first approval level. These events flow through the standard action processor pipeline and can trigger emails, WhatsApp messages, or any other configured channel.

**How it works:**

1. `WorkFlowEngine.EnterApprovalLevelAsync` records a `WorkflowTaskNotificationRequest` for each `TWORKFLOWTASK` row inserted (TaskId, AssigneeUserId, WorkflowInstanceId, WorkflowId, DataJson).
2. After the workflow transaction commits, `WorkFlowBLL.StartWorkflow` publishes one `TOUTBOX` event per notification with `EventTypeId = WORKFLOW_TASK_ASSIGNED`.
3. The action processor subscriber picks up the event and dispatches to all configured `MACTION` rows linked to that event type via `MEVENTTYPEACTION`.
4. Those `MACTION` rows carry `DIRECTACTIONID = 1` (WFAPPROVAL), so emails include Approve/Reject/Return links and WhatsApp messages include interactive buttons — automatically, with no additional code.

**Admin configuration required:**

```sql
-- 1. Insert a MEVENTTYPE row for the new event type
INSERT MEVENTTYPE (EVENTTYPEID, EVENTTYPECODE, EVENTTYPENAME, ...)
VALUES (<id>, 'WORKFLOW_TASK_ASSIGNED', 'Workflow Task Assigned', ...);

-- 2. Link it to the desired MACTION rows via MEVENTTYPEACTION
INSERT MEVENTTYPEACTION (EVENTTYPEID, ACTIONID) VALUES (<id>, <your_email_maction_id>);
INSERT MEVENTTYPEACTION (EVENTTYPEID, ACTIONID) VALUES (<id>, <your_whatsapp_maction_id>);

-- 3. Ensure those MACTION rows have DIRECTACTIONID = 1
UPDATE MACTION SET DIRECTACTIONID = 1 WHERE ACTIONID IN (...);
```

The email template must contain `##APPROVE_URL##`, `##REJECT_URL##`, `##RETURN_URL##` placeholders.
The WhatsApp template will have interactive buttons injected automatically.

No new code is needed. Adding or removing notification channels is pure admin configuration.

---

### Resend Notifications

`POST /Action/Resend` re-publishes a TOUTBOX event so fresh tokens are generated for an assignee who has not yet acted:

```json
{ "EventTypeId": <WORKFLOW_TASK_ASSIGNED event type id>, "ContextId": <TaskId> }
```

Old un-clicked tokens remain valid until expiry (48h) but the one-time-use check prevents double execution if both are clicked.

---

### Adding a New Actionable Entity Type

To add click-to-act for any new entity (example: Service Call allocation):

1. Insert `MDIRECTACTION` header:
   ```sql
   INSERT MDIRECTACTION (..., DIRECTACTIONCODE='SC_ASSIGN', CONTEXTIDFIELD='ServiceCallId',
     ASSIGNEEUSERIDFIELD='EngineerUserId', ...);
   ```

2. Insert `MDIRECTACTIONDETAIL` rows:
   ```sql
   -- Direct API call
   INSERT MDIRECTACTIONDETAIL (..., ACTIONCODE='SC_ACCEPT', EMAILPLACEHOLDER='ACCEPT_URL',
     BUTTONPAYLOADPREFIX='SC_ACCEPT', APIENDPOINT='/ServiceCall/AcceptAllocation',
     HTTPMETHOD='POST', PAYLOADTEMPLATE='{"ServiceCallId":{ContextId}}', NEEDSINPUT=0, FLOWID=-1);

   -- Conversational follow-up (collect denial reason via EIP flow)
   INSERT MDIRECTACTIONDETAIL (..., ACTIONCODE='SC_DENY', EMAILPLACEHOLDER='DENY_URL',
     BUTTONPAYLOADPREFIX='SC_DENY', NEEDSINPUT=1,
     FLOWID=<MEIPFLOWDEFINITION.FLOWID for SC_DENY_REASON_FLOW>);
   ```

3. In `MEVENTTYPEACTION`: link `SERVICE_CALL_ALLOCATED` event type → `MACTION` rows with `DIRECTACTIONID = <SC_ASSIGN id>`.

4. In `ServiceCallBLL.AllocateEngineer()`: publish TOUTBOX event with event type `SERVICE_CALL_ALLOCATED`.

**Zero new code.** Email links appear. Accept → direct API call. Deny → EIP conversational flow.

---

## 13. WhatsApp Integration Deep Dive

WhatsApp is the primary channel for EIP in most deployments. EIP supports two WhatsApp providers and handles both structured (template/button) and unstructured (free text) messages.

### Message Provider Support

EIP supports two WhatsApp Business API providers:

| Provider | Protocol | Authentication |
|----------|----------|---------------|
| **Celitix** | REST + TLS 1.2 | API key header |
| **Meta (Facebook Graph API)** | REST (v18.0) | Bearer token + Phone Number ID |

The active provider is selected via configuration. Both providers are configured independently and can be switched without code changes.

### Incoming Message Handling

When WhatsApp delivers a message to the GoodBooks webhook:

**Button message** (Mode 1) — two-tier routing:

**Primary path (legacy template approval):**
1. EIP detects the structured button payload
2. Checks if payload matches `ACCEPT_`, `APPROVE_`, `REJECT_`, `FORWARD_`, or `RETURN_` prefixes
3. Parses the payload format: `ACTION_TemplateId_ObjectId`
   - `ACTION`: ACCEPT / APPROVE maps to Accept (1), REJECT to Reject (2), FORWARD / RETURN to Forward (3)
   - `TemplateId`: identifies the mail template
   - `ObjectId`: the business record ID
4. Looks up the FlowCode from the mail template in MMAILTEMPLATE.FLOWCODE
5. Routes to TemplateApprovalBLL to execute the approval action

**Fallback path (Direct Action):**
1. If no legacy prefix matches, loads all active `MDIRECTACTIONDETAIL` rows from the DB
2. Searches for a row whose `BUTTONPAYLOADPREFIX` matches the start of the payload
3. Extracts ContextId from the suffix: `{BUTTONPAYLOADPREFIX}_{ContextId}` (e.g., `WF_APPROVE_5001`)
4. Invokes `GenericApiDirectActionHandler` with the matched `ActionCode` and ContextId
5. The handler substitutes `{ContextId}` into `PAYLOADTEMPLATE` and calls the configured `APIENDPOINT`

**Text message** (Mode 2):
1. EIP extracts the message text and sender phone number
2. Routes to EIPConversationBLL for conversational processing
3. Matching routing rules identify the correct flow
4. Session is restored if an active session exists
5. Flow engine processes the message and sends a reply

### Outgoing Message Types

EIP composes different WhatsApp message formats depending on context:

| Type | When used | Content |
|------|-----------|---------|
| **Text** | Simple messages, confirmations | Plain text body |
| **Template** | Notifications, approvals | Pre-approved WhatsApp template with variable substitution |
| **Interactive (buttons)** | CHOICE steps, approval requests | Up to 3 quick-reply buttons |
| **Media** | Documents, images | Media URL + caption |

### Retry Policy

WhatsApp API calls use an exponential backoff retry policy:
- **3 attempts** maximum
- Wait: 2 seconds, then 4 seconds, then 8 seconds
- Failure after 3 attempts returns an error result logged for admin review

---

## 14. Capabilities and Actions

EIP distinguishes between **Capabilities** (higher-level operations dispatched from CAPABILITY steps) and **Actions** (named handlers executed from ACTION steps or post-flow).

### Registered Action Handlers

These handlers execute within EIP flow ACTION steps or as post-flow actions.

#### SEND_NOTIFICATION

Routes an in-app notification through the ActionProcessor outbox pipeline. Creates entries in `TEVENTACTIONRUN` and `TACTIONOUTBOX`, which are picked up by the background worker and delivered via the appropriate channel.

Work is partitioned across 5 queues (`action-exec-p0` through `action-exec-p4`) based on tenant ID for load distribution.

#### CALL_EXTERNAL_API

Makes HTTP calls to external third-party APIs. Reads the target URL, HTTP method (GET/POST/PUT), and request body from the flow context payload. Stores the response body back in context for use by subsequent steps.

#### CALL_STATUS_UPDATE

Updates a CRM call record's status. Reads `CallId` and `ApprovalStatus` from context, then POSTs to the internal CRM service endpoint. Used in WhatsApp-based approval flows for call management.

#### CRUD_OPERATION

The most versatile handler — calls any internal GB5 service endpoint. Reads from context:
- `Endpoint` — relative path (e.g., `/Leave/SaveLeave`)
- `Method` — HTTP verb (default: POST)
- `Body` — JSON body template with `{ContextKey}` substitutions

This enables any GB5 business module to be accessed from a chat flow without writing custom code. The response is stored in `context.Payload["CrudResult"]`.

**Example: submitting a leave request via chat**

```json
"SUBMIT_LEAVE": {
  "StepType": "ACTION",
  "ActionCode": "CRUD_OPERATION",
  "Payload": {
    "Method": "POST",
    "Endpoint": "/Leave/SaveLeave",
    "Body": { "FromDate": "{LeaveFrom}", "ToDate": "{LeaveTo}", "LeaveTypeId": "{LeaveType}" }
  },
  "MessageTemplate": "Leave from {LeaveFrom} to {LeaveTo} submitted successfully.",
  "NextStepCode": "END"
}
```

#### VALIDATE_INPUT

Validates that a required field is present and non-empty in the payload. Used as a guard step before executing actions that require validated input.

---

## 15. The Six-Phase Execution Engine

Every incoming message passes through six phases. Each phase appends an entry to an execution trace that can be inspected for debugging.

```
Incoming Message
      |
      v
+-----------------------------------+
|  PHASE 1 — NORMALIZATION          |
|  Validate tenant, user, channel   |
|  Normalize message text           |
|  Build execution context          |
+-----------------------------------+
      |
      v
+-----------------------------------+
|  SESSION RESTORE                  |
|  Load active session              |
|  Restore CurrentStepCode          |
|  Restore context Variables        |
+-----------------------------------+
      |
      v
+-----------------------------------+
|  PHASE 2 — ROUTING                |
|  Load routing rules               |
|  Match message to flow            |
|  Assign FlowCode                  |
+-----------------------------------+
      |
      v
+-----------------------------------+
|  PHASE 3 — FLOW ENGINE            |
|  Load flow definition from DB     |
|  Execute steps sequentially       |
|  Pause at INPUT/CHOICE            |
|  Branch at CONDITION              |
|  Call APIs at ACTION              |
|  Collect and store context vars   |
+-----------------------------------+
      |
      v
+-----------------------------------+
|  SESSION SAVE / EXPIRE            |
|  Flow completed: expire session   |
|  Flow paused: save state          |
+-----------------------------------+
      |
      v
+-----------------------------------+
|  PHASE 4 — CAPABILITY ENGINE      |
|  (if CapabilityCode configured)   |
|  Risk assessment                  |
|  OTP generation / validation      |
|  Execute capability action        |
+-----------------------------------+
      |
      v
+-----------------------------------+
|  PHASE 5 — ACTION ENGINE          |
|  (if ActionCode configured)       |
|  Resolve registered handler       |
|  Idempotency check                |
|  Execute with timeout             |
+-----------------------------------+
      |
      v
+-----------------------------------+
|  PHASE 6 — RESPONSE ENGINE        |
|  Rate limiting (2s per user)      |
|  Select channel handler           |
|  Send via channel API             |
+-----------------------------------+
      |
      v
  Response delivered to user
```

### Idempotency in Phase 5

The Action Engine maintains an in-memory idempotency store. If a flow step supplies an `IdempotencyKey` and that key has already been executed in the current process lifetime, the action is skipped and the previous success result is returned. This prevents duplicate API calls if the same message is delivered more than once.

### Phase Tracing

Every phase appends an entry to the execution trace with:
- Phase name
- Status (START / SUCCESS / FAILED / SKIPPED / CANCELLED)
- Description and timestamp

This trace is returned in the execution result and can be used for debugging, monitoring, and audit purposes.

---

## 16. Channel Handlers — How Responses Are Delivered

After the flow engine produces a response message, the Response Engine selects the appropriate channel handler and delivers the message.

### Handler Selection

The handler is selected by channel name string (WHATSAPP, TEAMS, SLACK, TELEGRAM, SMS, POSTMAN). The mapping is built at startup from all registered `IEIPChannelHandler` implementations.

### Rate Limiting

The Response Engine enforces a **2-second cooldown per user per channel**. If two responses for the same user on the same channel are attempted within 2 seconds, the second is skipped. Stale rate limit entries are automatically evicted so the rate limiter does not grow unbounded.

### Response Context

Each channel handler receives a response context containing:
- `Message` — the text to send
- `Recipient` — the user's platform identifier
- `Buttons` — optional list of interactive button options
- `MediaUrl` — optional media attachment
- `Channel` — the target channel name
- `TenantId` and `FlowCode` — for logging and audit

### Teams — Adaptive Cards

Teams responses are sent as Adaptive Cards (v1.4). Text is rendered as a TextBlock and any buttons are rendered as `Action.OpenUrl` elements. The card is posted to a Teams incoming webhook URL configured per tenant.

### Slack — Block Kit

Slack responses use the Block Kit format with a section block containing markdown text. Posted to a Slack incoming webhook URL.

### WhatsApp — Dual Provider

WhatsApp responses are formatted based on content type (text, interactive, template, media) and sent to either the Celitix or Meta provider depending on configuration.

### Telegram — Bot API

Telegram responses are sent via the Telegram Bot API `sendMessage` endpoint. Supports HTML parse mode. The recipient can be configured globally or overridden per-message.

### SMS — Plain Text

SMS messages are trimmed to 160 characters maximum before delivery. SMS is one-way in the current implementation (send only; replies are handled separately via the webhook).

---

## 17. Routing Rules — Directing Traffic to the Right Flow

Routing rules determine which flow to execute for an incoming message. Rules are stored in `MEIPROUTINGRULE` and configured per tenant and channel.

### Rule Structure

Each routing rule has:
- **Priority** — lower number = higher priority (1 is checked first)
- **ChannelType** — which channel this rule applies to
- **MatchType** — how to compare the incoming message against the pattern
- **MatchValue** — the pattern to match
- **FlowCode** — the flow to execute if matched

### Match Types

| MatchType | Behaviour | Example |
|-----------|-----------|---------|
| 1 — Exact | Message equals the pattern exactly (case-insensitive) | `"apply leave"` |
| 2 — StartsWith | Message begins with the pattern | `"leave "` |
| 3 — Contains | Message contains the pattern anywhere | `"balance"` |
| 4 — Regex | Message matches a regular expression | `"^apply\s+(leave\|vacation)"` |

### Routing Example

| Priority | MatchType | MatchValue | FlowCode |
|----------|-----------|------------|----------|
| 10 | Exact | `apply leave` | `LEAVE_REQUEST_FLOW` |
| 20 | Exact | `check balance` | `LEAVE_BALANCE_FLOW` |
| 30 | Contains | `payslip` | `PAYSLIP_FLOW` |
| 99 | StartsWith | (empty) | `HR_MENU_FLOW` |

With these rules, "apply leave" routes to the leave request flow, "check payslip march" routes to the payslip flow, and any other message falls through to the HR menu flow.

### Fallback Behaviour

If no routing rule matches, EIP returns a generic "I didn't understand that" response. Implementing a catch-all rule (StartsWith with empty match value, lowest priority) is recommended for all production tenants.

---

## 18. Security and Multi-Tenancy

EIP is built for multi-tenant enterprise deployments. Security is enforced at every layer.

### Tenant Isolation

- Every database query includes a `TENANTID` filter — it is impossible to access another tenant's data through EIP
- Tenant ID comes from the authenticated session or the signed token — never from user-provided input
- Flow definitions are tenant-scoped; tenant-specific flows override global defaults
- Session state is tenant-scoped — users from different organisations cannot share sessions

### Token Security (Email Approval)

- Tokens are signed with HMAC-SHA256 using a secret stored in HashiCorp Vault
- Signature comparison uses constant-time algorithm to prevent timing attacks
- Each token is one-time-use; the SHA-256 hash is recorded on first use
- Tokens expire after 48 hours; expired tokens are rejected even if unused
- Token payload is URL-safe Base64 encoded — it contains only non-sensitive IDs (task ID, action code, tenant ID, expiry timestamp)

### Input Validation

- All INPUT step values are validated against configured patterns before being accepted
- SQL parameters are always parameterized — no dynamic SQL string concatenation
- HTML output (email approval pages) is HTML-encoded to prevent XSS

### Authentication Model

- WhatsApp/Teams/Slack messages are authenticated by the platform provider before reaching EIP
- Email approval links require no login but are cryptographically secured by signed tokens
- The EIP flow engine runs with a system-level login (UserId = -1) scoped to the correct tenant

### Audit Trail

| What | Where stored |
|------|-------------|
| Direct Action token use | `TDIRECTACTIONTOKEN` (TokenHash, ContextId, ActionCode, AssigneeUserId, UsedAt) |
| Session state changes | `TEIUSERSESSION` (LastActivityAt updates) |
| Workflow action executions | Workflow module (existing audit tables) |
| ActionProcessor events | `TEVENTACTIONRUN`, `TACTIONOUTBOX` |

---

## 19. For Administrators — Configuration Reference

### Application Settings

All sensitive configuration is stored in HashiCorp Vault. Non-sensitive configuration is in `appsettings.json`.

#### Direct Action Token Secret

```json
{
  "DirectAction": {
    "TokenSecret": "<loaded from Vault>"
  }
}
```

`TokenSecret` must be a cryptographically random string of at least 32 characters. Rotating this key invalidates all outstanding Direct Action links — plan rotation accordingly.

#### Internal Service URL

```json
{
  "SysJobSettings": {
    "ServiceBaseUrl": "https://gb5-internal.yourcompany.com"
  }
}
```

Used by CRUD_OPERATION, CALL_STATUS_UPDATE, and the email approval endpoint to call internal GB5 services.

#### Tenant Database Mapping

```json
{
  "Tenant": {
    "42": {
      "DatabaseName": "GB5_CompanyABC"
    }
  }
}
```

Required for email approval token processing — maps TenantId to the database name for data access.

#### WhatsApp — Celitix Provider

```json
{
  "MessageHubSettings": {
    "Celitix": {
      "Enable": true,
      "BaseUrl": "https://api.celitix.com",
      "MessageEndpoint": "/v1/messages",
      "WabaNumber": "919876543210",
      "Key": "<loaded from Vault>",
      "KeyHeaderName": "x-api-key",
      "WabaNumberHeaderName": "x-waba-number",
      "ContentType": "application/json"
    }
  }
}
```

#### WhatsApp — Meta (Facebook Graph API) Provider

```json
{
  "MessageHubSettings": {
    "WhatsApp": {
      "Enable": true,
      "AuthToken": "<loaded from Vault>",
      "PhoneNumberId": "123456789012345"
    }
  }
}
```

#### Microsoft Teams

```json
{
  "MessageHubSettings": {
    "Teams": {
      "WebhookUrl": "https://outlook.office.com/webhook/..."
    }
  }
}
```

#### Slack

```json
{
  "Slack": {
    "WebhookUrl": "https://hooks.slack.com/services/...",
    "DefaultChannel": "#erp-notifications",
    "DefaultUserName": "GoodBooks ERP",
    "DefaultIconEmoji": ":ledger:"
  }
}
```

#### Telegram

```json
{
  "MessageHubSettings": {
    "Telegram": {
      "BotToken": "<loaded from Vault>",
      "BaseUrl": "https://api.telegram.org",
      "DefaultRecipient": ""
    }
  }
}
```

#### RabbitMQ (ActionProcessor)

```json
{
  "RabbitMQ": {
    "Host": "rabbitmq.yourcompany.com",
    "UserName": "<from Vault>",
    "Password": "<from Vault>",
    "VirtualHost": "/"
  }
}
```

---

## 20. For Implementation Teams — Setting Up a Flow

This section walks through everything needed to deploy a new conversational flow end-to-end.

### Step 1: Design the Conversation

Before writing any configuration, map the conversation as a flowchart:
- What does the user type to start? (this becomes the routing rule)
- What questions need to be asked?
- What validations apply?
- Are there branches (e.g., different paths for annual leave vs sick leave)?
- What API call submits the data?
- What confirmation message closes the conversation?

### Step 2: Define the Flow JSON

Write the flow definition following the structure in Section 8. Key rules:
- Every step has a unique `StepCode` (the dictionary key)
- Every step except END has a `NextStepCode` pointing to the next step
- INPUT and CHOICE steps pause execution and wait for the user
- Use `ContextKey` on INPUT steps to name what is being collected
- Use `{ContextKey}` in `MessageTemplate` values to reference collected data

### Step 3: Save the Flow to the Database

Insert the flow definition into `MEIPFLOWDEFINITION`:

```sql
INSERT INTO MEIPFLOWDEFINITION 
    (FLOWCODE, FLOWNAME, FLOWDEFINITIONJSON, STATUS, TENANTID, CREATEDON, MODIFIEDON)
VALUES 
    ('LEAVE_REQUEST_FLOW', 'Leave Request', '<flow json>', 1, -1, GETUTCDATE(), GETUTCDATE())
```

Use `TENANTID = -1` for a global flow available to all tenants. Use a specific `TENANTID` to override for one organisation.

### Step 4: Create Routing Rules

For each channel the flow should be accessible on, create a routing rule in `MEIPROUTINGRULE`:

```sql
INSERT INTO MEIPROUTINGRULE
    (TENANTID, CHANNELTYPE, MATCHTYPE, MATCHVALUE, FLOWCODE, PRIORITY, STATUS)
VALUES
    (42, 1, 1, 'apply leave', 'LEAVE_REQUEST_FLOW', 10, 1)
```

Channel type values: 0=Postman, 1=WhatsApp, 2=Teams, 3=Telegram, 4=Slack, 5=SMS  
Match type values: 1=Exact, 2=StartsWith, 3=Contains, 4=Regex

### Step 5: Link to Mail Template (for Button Flows)

If the flow is triggered by a WhatsApp button message from a mail template:

```sql
UPDATE MMAILTEMPLATE
SET FLOWCODE = 'LEAVE_REQUEST_FLOW'
WHERE MAILTEMPLATEID = 1042
```

### Step 6: Configure Direct Action Buttons (for Workflow Tasks and Any Entity)

To enable one-click email approvals or WhatsApp buttons for a workflow task:

1. Ensure `MACTION.DIRECTACTIONID` is set to the relevant `MDIRECTACTION.DIRECTACTIONID` (e.g., `1` for WFAPPROVAL).
2. The `ActionEventDto` sent to the ActionProcessor must include `DirectActionId` and `ContextId`:

```json
{
  "DirectActionId": 1,
  "ContextId": 431
}
```

The email template must contain the placeholders `##APPROVE_URL##`, `##REJECT_URL##`, and `##RETURN_URL##` (matching `MDIRECTACTIONDETAIL.EMAILPLACEHOLDER` values) where the links should appear. EIP replaces these with the actual signed URLs before sending. WhatsApp buttons are injected automatically from `MDIRECTACTIONDETAIL.BUTTONPAYLOADPREFIX` — no template changes needed for WA.

### Step 7: Test

Use the **Postman channel** for initial testing. Set `ChannelType = 0` (Postman) in your test requests. The Postman handler logs the full payload and returns a simulated success without sending any real messages, so you can verify the flow logic without affecting real users.

### Common Pitfalls

| Mistake | Symptom | Fix |
|---------|---------|-----|
| Missing routing rule | Message not recognised, no response | Add routing rule for the channel and match pattern |
| Wrong NextStepCode | Flow terminates early or loops | Check all step codes and NextStepCode references for typos |
| ContextKey mismatch | Literal `{Key}` appears in message output | Ensure ContextKey in INPUT step matches exactly the `{Key}` token in MESSAGE template |
| Flow not found | Engine logs "flow not found" | Verify FlowCode in DB matches routing rule; check STATUS = 1 |
| Missing FLOWCODE on mail template | Button messages fail | Update MMAILTEMPLATE.FLOWCODE for the relevant template |
| Session expiry mid-test | Flow restarts unexpectedly | Sessions expire after 30 minutes of inactivity — by design |

### Example: Complete HR Menu Flow

This example shows a multi-branch menu flow that a user can trigger by typing "hi" on WhatsApp.

**Routing rule:** MatchType=1 (Exact), MatchValue="hi", FlowCode="HR_MENU_FLOW"

```json
{
  "FlowCode": "HR_MENU_FLOW",
  "StartStepCode": "WELCOME",
  "Steps": {
    "WELCOME": {
      "StepType": "MESSAGE",
      "MessageTemplate": "Welcome to GoodBooks HR! How can I help you?",
      "NextStepCode": "MAIN_MENU"
    },
    "MAIN_MENU": {
      "StepType": "CHOICE",
      "MessageTemplate": "Please select an option:",
      "Choices": {
        "Check Leave Balance": "FETCH_BALANCE",
        "Apply Leave": "ASK_LEAVE_FROM",
        "View Payslip": "PAYSLIP_INFO"
      }
    },
    "FETCH_BALANCE": {
      "StepType": "ACTION",
      "ActionType": "CALL_API",
      "ApiConfig": { "Url": "/Leave/GetBalance?UserId={UserId}", "Method": "GET" },
      "ContextKey": "Balance",
      "NextStepCode": "SHOW_BALANCE"
    },
    "SHOW_BALANCE": {
      "StepType": "MESSAGE",
      "MessageTemplate": "You have {Balance} leave days remaining.",
      "NextStepCode": "END"
    },
    "ASK_LEAVE_FROM": {
      "StepType": "INPUT",
      "MessageTemplate": "Enter start date (DD-MM-YYYY):",
      "ContextKey": "LeaveFrom",
      "Validation": { "Required": true, "Regex": "^\\d{2}-\\d{2}-\\d{4}$", "ErrorMessage": "Use DD-MM-YYYY." },
      "NextStepCode": "ASK_LEAVE_TO"
    },
    "ASK_LEAVE_TO": {
      "StepType": "INPUT",
      "MessageTemplate": "Enter end date (DD-MM-YYYY):",
      "ContextKey": "LeaveTo",
      "Validation": { "Required": true },
      "NextStepCode": "SUBMIT_LEAVE"
    },
    "SUBMIT_LEAVE": {
      "StepType": "ACTION",
      "ActionCode": "CRUD_OPERATION",
      "Payload": { "Method": "POST", "Endpoint": "/Leave/SaveLeave" },
      "MessageTemplate": "Leave from {LeaveFrom} to {LeaveTo} submitted.",
      "NextStepCode": "END"
    },
    "PAYSLIP_INFO": {
      "StepType": "MESSAGE",
      "MessageTemplate": "Your latest payslip is available on the GoodBooks portal.",
      "NextStepCode": "END"
    },
    "END": { "StepType": "END" }
  }
}
```

---

## 21. For Developers — Architecture and Integration

### Project Structure

EIP code is split across three layers following the standard GB5 architecture:

```
GB5Framework/
|-- FrameworkBLL/
|   |-- EIPConversation/
|   |   |-- EIPHandlers/
|   |   |   |-- ChannelHandler/           One class per channel
|   |   |   |-- EIPActionExecutor/        Dispatches to action handlers
|   |   |   |-- EIPConversationBLL/       Entry point
|   |   |   |-- EIPConversationEngine/    6-phase orchestrator
|   |   |   |-- EIPEngine/
|   |   |   |   |-- ActionEngine/         Executes handlers with idempotency
|   |   |   |   |-- ActionHandler/        Named handlers (CRUD, notify, etc.)
|   |   |   |   |-- CapabilityEngine/     Capability dispatch
|   |   |   |   |-- ChannelNormalizer/    Normalizes raw message
|   |   |   |   |-- FlowEngine/           Multi-step flow execution
|   |   |   |   |-- ResponseEngine/       Rate limiting + delivery
|   |   |   |   `-- RoutingEngine/        Message-to-flow matching
|   |   |   |-- EIPFlowRepository/        Loads flow definitions
|   |   |   |-- EIPOTP/                   OTP generation/verification
|   |   |   |-- EIPRisk/                  Risk assessment
|   |   |   `-- EIPWebhook/               External webhook ingestion
|   |-- ActionProcessor/
|   |   |-- DirectAction/                        ← replaces EmailApprovalToken/
|   |   |   |-- IDirectActionTokenService.cs     ← HMAC-SHA256 token gen/validation
|   |   |   |-- DirectActionTokenService.cs      ← assigneeUserId in payload
|   |   |   |-- IGenericApiDirectActionHandler.cs
|   |   |   `-- GenericApiDirectActionHandler.cs ← DB-config-driven API caller
|   |   `-- Handlers/EmailActionHandler.cs       ← uses DirectActionId; generates tokens per button
|   `-- MessageHubGenerator/
|       |-- MessageHubEngine/
|       |   `-- MessageHubGeneratorEngine.cs     ← injects DirectAction buttons for WA/Teams
|       `-- MessageHubWebhook/
|           `-- MessageHubWebhookBLL.cs          ← DirectAction fallback button routing
|-- FrameworkDAL/
|   |-- CustomCode/DirectAction/                 ← NEW
|   |   |-- IDirectActionTokenDAL.cs
|   |   |-- DirectActionTokenDAL.cs              ← hits TDIRECTACTIONTOKEN
|   |   |-- IDirectActionConfigDAL.cs
|   |   `-- DirectActionConfigDAL.cs             ← reads MDIRECTACTION + MDIRECTACTIONDETAIL
|   |-- CustomCode/EIPConversation/              DAL interfaces and implementations
|   |-- DTO/DirectAction/                        ← NEW
|   |   |-- DirectActionDTO.cs
|   |   |-- DirectActionDetailDTO.cs
|   |   `-- DirectActionTokenDTO.cs
|   |-- DTO/EIPConversation/                     Data transfer objects
|   |-- Query/DirectAction/                      ← NEW
|   |   |-- DirectActionTokenQB.cs
|   |   `-- DirectActionConfigQB.cs
|   `-- Query/EIPConversation/                   SQL query constants
`-- FrameworkSL/
    `-- Endpoints/Action/                        ← replaces Endpoints/Workflow/ for token actions
        |-- ActionExecuteEndpoint.cs             ← GET /Action/Execute?token=...
        `-- ResendDirectAction.cs                ← POST /Action/Resend
```

### Key Interfaces

| Interface | Purpose |
|-----------|---------|
| `IEIPConversationBLL` | Entry point for all incoming messages |
| `IEIPConversationEngine` | 6-phase execution orchestrator |
| `IEIPFlowEngine` | Executes a single flow from current step |
| `IEIPRoutingEngine` | Resolves FlowCode from routing rules |
| `IEIPCapabilityEngine` | Executes capability handlers |
| `IEIPActionEngine` | Executes action handlers with idempotency |
| `IEIPResponseEngine` | Delivers responses via channel handlers |
| `IEIPChannelHandler` | Delivers messages to a specific platform |
| `IEIPActionHandler` | Handles a named action (ActionCode) |
| `IEIPSessionDAL` | Reads/writes conversation session state |
| `IEIPFlowDAL` | Reads/writes flow definitions |
| `IDirectActionTokenService` | Generates and validates signed tokens (replaces `IEmailApprovalTokenService`) |
| `IDirectActionTokenDAL` | One-time use check against `TDIRECTACTIONTOKEN` |
| `IDirectActionConfigDAL` | Reads `MDIRECTACTION` + `MDIRECTACTIONDETAIL` |
| `IGenericApiDirectActionHandler` | Executes DB-configured action (HTTP call or EIP flow) |

### Adding a New Channel

1. Create a class implementing `IEIPChannelHandler` in `FrameworkBLL/EIPConversation/EIPHandlers/ChannelHandler/`
2. Set `ChannelName` to a unique uppercase string (e.g., `"MESSENGER"`)
3. Implement `SendAsync` to call the platform's message API
4. The class is auto-discovered by DI assembly scanning — no manual registration needed
5. Add a new value to the `EIPChannelType` enum in the DTO project
6. Add a case in `EIPResponseEngine.GetChannelString()` to map the enum to the string

### Adding a New Action Handler

1. Create a class implementing `IEIPActionHandler` in `FrameworkBLL/EIPConversation/EIPHandlers/EIPEngine/ActionHandler/`
2. Set `ActionCode` to a unique uppercase string (e.g., `"SEND_EMAIL_DIGEST"`)
3. Implement `HandleAsync` with the business logic
4. The class is auto-discovered and registered in `EIPActionExecutor` at startup — no code changes needed elsewhere

### Named HTTP Clients

EIP uses named HTTP clients registered in the DI container. Never use `new HttpClient()`.

| Client name | Used by | Target |
|-------------|---------|--------|
| `eip-internal` | ACTION step CALL_API | Internal GB5 services |
| `eip-integration` | IntegrationActionHandler | External third-party APIs |
| `crm-internal` | CallStatusUpdateAction | CRM module |
| `gb5-internal` | CrudOperationHandler | Any internal GB5 endpoint |

### Key DTOs

**`EIPConversationDTO`** — the incoming message payload:

| Property | Description |
|----------|-------------|
| `Message` | The user's text |
| `UserIdentifier` | Platform user ID (phone number, Teams ID, etc.) |
| `TenantId` | Organisation (as string) |
| `ChannelType` | Enum value |
| `FlowCode` | If known, skips routing (optional) |

**`EIPExecutionContextDTO`** — the mutable execution context threaded through all phases:

| Property | Description |
|----------|-------------|
| `Variables` | `Dictionary<string, object>` — persisted context state |
| `NormalizedMessage` | Cleaned, trimmed message text |
| `CurrentStepCode` / `NextStepCode` | Flow position tracking |
| `ResponseMessage` | The reply to send back to the user |
| `PhaseTrace` | Execution log for debugging |
| `Payload` | `Dictionary<string, object>` — runtime data for capability/action handlers |

**`EIPFlowStepDTO`** — a single step in a flow:

| Property | Used by |
|----------|---------|
| `StepType` | All steps |
| `MessageTemplate` | MESSAGE, INPUT, CHOICE, ACTION |
| `NextStepCode` | All steps except END |
| `ContextKey` | INPUT, ACTION (for storing results) |
| `Validation` | INPUT (Required, Regex, ErrorMessage) |
| `Choices` | CHOICE (dictionary of label → NextStepCode) |
| `ConditionKey`, `Conditions`, `DefaultNextStepCode` | CONDITIONAL |
| `ActionType`, `ApiConfig`, `EntityId` | ACTION |
| `CapabilityCode` | CAPABILITY |

### Dependency Injection

All BLL and DAL classes are auto-registered via assembly scanning at startup:

```csharp
services.AddScopedFromAssembly(typeof(IEIPConversationBLL).Assembly);
services.AddScopedFromAssembly(typeof(IEIPFlowDAL).Assembly);
```

No manual `services.AddScoped<>()` calls are needed for new implementations. Implement the interface, place the class in the correct assembly, and DI picks it up automatically.

### ConfigureAwait(false)

All `await` calls in BLL and DAL must use `.ConfigureAwait(false)`. This is mandatory in library code (non-UI context) to avoid deadlocks and unnecessary synchronisation context switching.

---

## 22. Database Tables Reference

### Core EIP Tables

| Table | Purpose |
|-------|---------|
| `MEIPFLOWDEFINITION` | Flow definitions (FlowCode, StartStepCode, JSON steps) |
| `MEIPROUTINGRULE` | Routing rules — maps messages to flows per tenant and channel |
| `MEIPTENANT` | Registered tenants for EIP |
| `MEIPCHANNELENDPOINT` | Channel endpoint configuration per tenant |
| `TEIUSERSESSION` | Active conversation sessions (multi-turn state) |

### EIP Session Table (TEIUSERSESSION)

| Column | Type | Description |
|--------|------|-------------|
| `SESSIONID` | UNIQUEIDENTIFIER | Primary key (auto-generated) |
| `USERIDENTIFIER` | NVARCHAR(100) | Platform user ID |
| `TENANTID` | INT | Organisation |
| `CHANNELTYPE` | TINYINT | Channel enum value |
| `FLOWCODE` | NVARCHAR(100) | Active flow |
| `CURRENTSTEPCODE` | NVARCHAR(100) | Where the conversation is paused |
| `CONTEXTJSON` | NVARCHAR(MAX) | Serialized context variables |
| `STARTEDAT` | DATETIME | Session start (UTC) |
| `LASTACTIVITYAT` | DATETIME | Last message time — drives 30-min expiry |
| `STATUS` | TINYINT | 1=Active, 0=Expired |

Index: `IX_TEIUSERSESSION_LOOKUP` on `(USERIDENTIFIER, TENANTID, CHANNELTYPE) WHERE STATUS = 1`

### Direct Action Tables

#### MDIRECTACTION (Master — Action Group)

| Column | Type | Description |
|--------|------|-------------|
| `DIRECTACTIONID` | INT | Primary key (app-generated) |
| `DIRECTACTIONCODE` | NVARCHAR(20) | Unique code (e.g., `WFAPPROVAL`, `SC_ASSIGN`) |
| `DIRECTACTIONNAME` | NVARCHAR(200) | Display name |
| `CONTEXTIDFIELD` | NVARCHAR(200) | JSON field name holding the context entity PK |
| `ASSIGNEEUSERIDFIELD` | NVARCHAR(200) | JSON field name holding the assignee user ID |
| `STATUS` | TINYINT | 1=Active, 2=Deleted |
| `TENANTID` | INT | -1 = global; specific = tenant-scoped |
| Standard fields | — | VERSION, SORTORDER, CREATEDBYID, CREATEDON, MODIFIEDBYID, MODIFIEDON, SOURCETYPE |

Unique: `(DIRECTACTIONCODE, TENANTID)`

#### MDIRECTACTIONDETAIL (Child — One Row per Button/Link)

| Column | Type | Description |
|--------|------|-------------|
| `DIRECTACTIONDETAILID` | INT | Primary key |
| `DIRECTACTIONID` | INT | FK to MDIRECTACTION |
| `SLNO` | SMALLINT | Display order |
| `ACTIONCODE` | NVARCHAR(20) | Unique action identifier embedded in signed token |
| `ACTIONLABEL` | NVARCHAR(200) | Button/link text |
| `EMAILPLACEHOLDER` | NVARCHAR(100) | Template placeholder (e.g., `APPROVE_URL`) — `##APPROVE_URL##` in email body |
| `BUTTONPAYLOADPREFIX` | NVARCHAR(100) | WA/Teams button payload prefix (e.g., `WF_APPROVE`) |
| `APIENDPOINT` | NVARCHAR(500) | Internal endpoint (e.g., `/WorkFlow/WorkFlowActions`) |
| `HTTPMETHOD` | NVARCHAR(10) | POST / PUT / PATCH / DELETE |
| `PAYLOADTEMPLATE` | NVARCHAR(MAX) | JSON body; `{ContextId}` substituted at runtime |
| `NEEDSINPUT` | TINYINT | 0=Direct API call; 1=Conversational EIP flow |
| `FLOWID` | INT | FK to MEIPFLOWDEFINITION when NEEDSINPUT=1; -1 otherwise |

Unique: `(ACTIONCODE, DIRECTACTIONID)`

#### TDIRECTACTIONTOKEN (One-Time Use Audit — replaces TWORKFLOWEMAILTOKEN)

| Column | Type | Description |
|--------|------|-------------|
| `TOKENHASH` | NVARCHAR(500) | SHA-256 hash of the token (primary key) |
| `CONTEXTID` | INT | Context entity PK (was `TASKID`) |
| `ACTIONCODE` | NVARCHAR(20) | Action executed (was `ACTION TINYINT` — now human-readable) |
| `ASSIGNEEUSERID` | INT | User who clicked (for audit trail) |
| `TENANTID` | INT | Organisation |
| `EXPIRESAT` | DATETIME | Token expiry (UTC) |
| `USEDAT` | DATETIME | When clicked (null if unused) |
| `STATUS` | TINYINT | 0=Unused, 1=Used, 2=Revoked |
| `CREATEDON` | DATETIME | Token generation timestamp |

#### MACTION — new column

| Column | Type | Default | Description |
|--------|------|---------|-------------|
| `DIRECTACTIONID` | INT | -1 | FK to MDIRECTACTION; -1 = one-way notification, no buttons |

### Mail Template Extension

| Column | Table | Description |
|--------|-------|-------------|
| `FLOWCODE` | `MMAILTEMPLATE` | Links the template to an EIP flow for button-message routing |

### ActionProcessor Tables (used by EIP notifications)

| Table | Purpose |
|-------|---------|
| `TEVENTACTIONRUN` | Tracks each action execution (status, timestamps, result) |
| `TACTIONOUTBOX` | Outbox entries for reliable at-least-once message delivery |
| `LPROCESSEDACTION` | Idempotency log — prevents duplicate processing |

---

## 23. Glossary

| Term | Definition |
|------|-----------|
| **EIP** | Enterprise Integration Platform — the GoodBooks conversational automation engine |
| **Flow** | A configured conversation script stored in the database, consisting of ordered steps |
| **FlowCode** | The unique identifier for a flow (e.g., `LEAVE_REQUEST_FLOW`) |
| **Step** | A single node in a flow — defines what action to take (ask, send, branch, call API) |
| **StepType** | The type of step: MESSAGE, INPUT, CHOICE, CONDITION, CONDITIONAL, ACTION, CAPABILITY, EXTERNAL_ACTION, END |
| **ContextKey** | The name of a variable stored in session context during an INPUT step |
| **Context Variable** | A named value collected during a conversation, referenced with `{ContextKey}` syntax |
| **Session** | The persisted state of an in-progress conversation stored in TEIUSERSESSION |
| **Routing Rule** | A database-configured pattern that maps an incoming message to a FlowCode |
| **Channel** | The messaging platform: WhatsApp, Teams, Slack, Telegram, SMS |
| **ChannelType** | Enum value identifying the channel (0=Postman, 1=WhatsApp, 2=Teams, 3=Telegram, 4=Slack, 5=SMS) |
| **Mode 1** | Button/template response — one tap on a structured button = one business action |
| **Mode 2** | Conversational — multi-turn text dialogue guided by a flow |
| **ActionHandler** | A registered class that executes a named action identified by ActionCode |
| **CapabilityCode** | Identifier for a capability handler (e.g., risk check, OTP, CRUD) |
| **Direct Action** | A DB-configured, click-to-act button or link embedded in a notification — executes an internal API call or launches an EIP flow when clicked |
| **MDIRECTACTION** | Master table grouping related action buttons (e.g., Approve + Reject + Return for workflow approvals) |
| **MDIRECTACTIONDETAIL** | Child table — one row per button/link within a group, carrying endpoint, payload template, and placeholder mappings |
| **TDIRECTACTIONTOKEN** | One-time use audit table for Direct Action tokens (replaces TWORKFLOWEMAILTOKEN; broader scope) |
| **ActionCode** | Unique string identifying a single action within a group (e.g., `WORKFLOW_APPROVE`); encoded in the signed token |
| **ContextId** | The entity PK passed through the signed token and into the API payload (e.g., WorkflowTaskId) |
| **AssigneeUserId** | User bound to the token — ensures the audit trail records the correct actor even for email approvals |
| **ButtonPayloadPrefix** | The prefix portion of a WhatsApp/Teams button payload (e.g., `WF_APPROVE`) — ContextId appended at runtime (`WF_APPROVE_5001`) |
| **EmailPlaceholder** | The `##NAME##` token in an email template body that gets replaced with the signed URL |
| **GenericApiDirectActionHandler** | BLL service that loads action config from DB and executes the configured HTTP call |
| **NEEDSINPUT** | Flag on MDIRECTACTIONDETAIL: 0=direct one-call action; 1=conversational EIP flow required |
| **WORKFLOW_TASK_ASSIGNED** | Event type code published to TOUTBOX when a workflow task is created — triggers assignment notifications |
| **Signed Token** | HMAC-SHA256 signed string embedded in Direct Action links — payload encodes actionCode, contextId, tenantId, assigneeUserId, and expiry ticks |
| **One-time use** | Tokens are recorded in TDIRECTACTIONTOKEN on first use and rejected on any subsequent use |
| **Tenant** | A GoodBooks customer organisation with its own data, flows, routing rules, and configuration |
| **Phase Trace** | The execution log appended by each of the 6 phases, returned for debugging |
| **ActionProcessor** | The background service that processes outbox events (email, notifications, etc.) |
| **MessageHub** | The GoodBooks module that manages outgoing channel messages (templates, approvals) |
| **CRUD_OPERATION** | A capability handler that calls any internal GB5 endpoint — enables any ERP action from a chat flow |
| **Idempotency** | The guarantee that the same action is not executed twice even if the same message is received more than once |
| **Rate Limiter** | The 2-second per-user cooldown enforced by the Response Engine to prevent message flooding |

---

*GoodBooks GB5 — EIP Platform*  
*For support, implementation queries, or to report issues, contact the GoodBooks development team.*
