---
name: workflow
description: |
  Creates and configures automated SmartStack workflows.
  Use this skill when:
  - User wants to automate actions (emails, webhooks)
  - User mentions "workflow", "automation", "trigger"
  - Creating a business process with steps
  - Integrating transactional emails
  Types: SendEmail, Wait, Condition, Webhook
---

# Skill Workflow SmartStack

> **Architecture:** Trigger -> Steps -> Actions (Email/Wait/Condition/Webhook)

**Reference:** [_shared.md](../_shared.md) for common services

## When This Skill Activates

| Trigger | Example |
|---------|---------|
| Explicit request | "Create a workflow for registration" |
| Automatic email | "Send an email when a ticket is created" |
| Chaining | "After 24h without response, send a reminder" |
| Keywords | "workflow", "trigger", "automation", "email template" |

## Flow

```
EVENT -> WorkflowTrigger("user.registered")
    |
IWorkflowService.TriggerAsync(code, variables)
    |
Workflow Steps: SendEmail -> Wait -> Condition -> Webhook
```

## Available Triggers

### User Events
| Trigger | Variables |
|---------|-----------|
| `user.registered` | userId, email, displayName, confirmUrl |
| `user.email-confirmed` | userId, email, displayName |
| `user.password-reset-requested` | userId, email, resetUrl |
| `user.account-locked/unlocked` | userId, email, lockReason |

### Ticket Events
| Trigger | Variables |
|---------|-----------|
| `ticket.created` | ticketId, ticketNumber, title, creatorEmail |
| `ticket.resolved/assigned` | ticketId, ticketNumber, assigneeName |
| `ticket.sla-warning/breached` | ticketId, deadline, remainingMinutes |

### Add a Trigger
```csharp
// WorkflowTriggerConfiguration.cs - GetSeedData()
new { Id = Guid.Parse("..."), Code = "entity.event", Name = "Entity Event",
    AvailableVariablesJson = JsonSerializer.Serialize(new[]{ ... }), ... }

// Trigger
await _workflowService.TriggerAsync("entity.event", new Dictionary<string, object>{...}, language: "en");
```

## Implementation

Start with [steps/step-00-init.md](steps/step-00-init.md) to identify workflow triggers and requirements, then proceed to [steps/step-01-implementation.md](steps/step-01-implementation.md) for:
- Step types (SendEmail, Wait, Condition, Webhook) with code templates
- Workflow creation and seed data
- Triggering from services
- Email templates (Handlebars syntax)
- Frontend API

## Relation with Domain Events

Workflow triggers (`TriggerAsync`) are the **primary mechanism** for cross-process automation (emails, webhooks, multi-step flows).

**Domain Events** (MediatR `INotificationHandler<T>`) are complementary for **in-process** side effects (audit logging, cache invalidation, read-model updates).

| Scenario | Mechanism |
|----------|-----------|
| Send email after entity creation | Workflow Trigger |
| Invalidate cache after entity update | Domain Event |
| Log audit trail | Domain Event |
| Multi-step business process with delays | Workflow Trigger |
| Notify multiple services of same change | Domain Event -> Workflow Trigger |

See `review-code/references/smartstack-conventions.md` (Domain Events section) for full pattern.

## Checklist

```
- Trigger identified (existing or new in WorkflowTriggerConfiguration.cs)
- Workflow created: Unique code, Linked trigger, Priority (10=standard, 20+=priority)
- Steps: SendEmail+templateId, Wait+delayMinutes, Condition+expression, Webhook+url
- Email templates created (if SendEmail)
- Trigger added to source code
- Tests: trigger, emails, variables
```

## Absolute Rules

| DO | DON'T |
|----|-------|
| IWorkflowService.TriggerAsync | Direct execution |
| All trigger variables | Hardcoded URLs in templates |
| Specify language for emails | Secrets in variables |
| Unique kebab-case codes | Infinite loops (A->B->A) |
| Log executions | Forget email templates |

## WorkflowVersion (R18 — v3.46+) — immutable snapshots

Every time a `Workflow` definition (metadata or steps) is modified, the application creates a `WorkflowVersion` snapshot:

```csharp
class WorkflowVersion
{
    public Guid Id { get; }
    public Guid WorkflowId { get; }
    public int VersionNumber { get; }            // Monotonic per workflow, starts at 1
    public string? ChangeDescription { get; }    // Commit-message style, optional
    public string SnapshotJson { get; }          // { workflow: {…}, steps: [{…}] } — self-contained
    public DateTime CreatedAt { get; }
    public string? CreatedBy { get; }
}
```

**Properties:**
- **Immutable** — once created, never updated. To "revert" a workflow → create a new version derived from a historical snapshot.
- **Self-contained JSON** — reading a snapshot is a JSON parse, not a query. Snapshots survive schema migration.
- Stored alongside the live workflow in `wkf_WorkflowVersions`.

**Use cases:**
- Audit trail (who changed what, when, why)
- Compare two versions side-by-side in admin UI
- Roll back a faulty workflow change without losing history
- Re-execute a paused execution against the version that was active when it started

**DO / DON'T:**
- DO populate `ChangeDescription` — empty descriptions blind operators
- DO surface version history in the admin UI on each `Workflow` detail page
- DON'T mutate or delete versions (the table should have only INSERT permissions in DB)
- DON'T snapshot on every single field change — batch via "Save" UX action

## WorkflowExecution.PausedAtStepOrder (R6 — Wait step resumption)

`WorkflowExecution` exposes `PausedAtStepOrder` (nullable int) to support `Wait` step suspension:

```csharp
// Suspend on a Wait step — Hangfire job will resume later
execution.PauseUntilResume(nextStepOrder: 5);
// Status becomes WaitingForResume (6), PausedAtStepOrder = 5

// When the Hangfire job fires :
execution.MarkResumed();
// Status becomes Running, PausedAtStepOrder reset to null
// Then the resumer iterates from PausedAtStepOrder onward
```

**Status enum (with v3.46+ value 6):**
```csharp
enum WorkflowExecutionStatus
{
    Pending = 0, Running = 1, Completed = 2, Failed = 3,
    PartiallyCompleted = 4, Cancelled = 5,
    WaitingForResume = 6  // R6 — Wait step in progress, Hangfire scheduled
}
```

**Implementation note:** the Hangfire job is scheduled by `WorkflowExecutionService` when a Wait step is encountered. The job's payload is the execution Id ; on fire, it loads the execution, calls `MarkResumed()`, and continues iterating from `PausedAtStepOrder`.

**DO / DON'T:**
- DO use `WaitingForResume` exclusively for Wait steps (not for other "in progress with delay" patterns)
- DO ensure the Hangfire job is idempotent (executions may resume more than once if the job retries)
- DON'T poll executions in `WaitingForResume` — Hangfire owns the scheduling
- DON'T remove `PausedAtStepOrder` history from completed executions (audit trail)

## Key Files

| File | Role |
|------|------|
| `Domain/Communications/Workflow.cs` | Workflow entity |
| `Domain/Communications/WorkflowStep.cs` | Step entity |
| `Domain/Communications/WorkflowTrigger.cs` | Trigger entity |
| `Domain/Communications/WorkflowVersion.cs` | Immutable snapshot (R18 — v3.46+) |
| `Domain/Communications/WorkflowExecution.cs` | Execution + `PausedAtStepOrder` (R6 — v3.46+) |
| `Application/Common/Interfaces/IWorkflowService.cs` | Interface |
| `Infrastructure/Services/Workflow/WorkflowExecutionService.cs` | Implementation (Hangfire scheduling for Wait) |
| `Infrastructure/.../WorkflowConfiguration.cs` | EF Config + Seed |

<success_criteria>
- Workflow entity, steps, and triggers created following SmartStack patterns
- EF Core configuration and seed data generated
- IWorkflowService integration wired correctly
- Email templates created with i18n support (4 languages)
</success_criteria>
