By Patrick McCurley

First Claim Slack Notification

By Patrick McCurley · Created Mar 20, 2026 public

When a client creates their very first claim, post a notification to #portal-feed — the same channel that receives courier login notifications.

Architecture

graph TD
    subgraph "Claim Creation Sources"
        M[Manual - Portal UI]
        S[Shopify Integration]
        Z[Zendesk Integration]
    end

    M --> VM["NewClaimFormViewModel<br/>.SubmitClaim()"]
    S --> SVM["ShopifyNewClaimFromViewModel<br/>(inherits SubmitClaim)"]
    Z --> ZVM["ZendeskNewClaimViewModel<br/>.SubmitClaim() override"]

    SVM --> VM
    ZVM --> BASE["TryNotifyFirstClaimAsync()"]
    VM --> BASE

    BASE --> DB{Any other claims<br/>for this client?}
    DB -->|No| SLACK["#portal-feed<br/>First claim created :tada:"]
    DB -->|Yes| SKIP[Skip]

    style DB fill:#fff3e0
    style SLACK fill:#e8f5e9
    style SKIP fill:#fce4ec
    style BASE fill:#e1f5fe

All three creation paths converge through NewClaimFormViewModel — the base view model. No Temporal dependency involved.

Flow

sequenceDiagram
    participant UI as Blazor Component
    participant VM as NewClaimFormViewModel
    participant SVC as ClaimFormService
    participant DB as Database
    participant SLACK as #portal-feed

    UI->>VM: SubmitClaim()
    VM->>SVC: SaveAsClaim()
    SVC->>DB: INSERT claim
    DB-->>SVC: OK
    SVC-->>VM: SaveClaimResult (success)

    Note over VM: TryNotifyFirstClaimAsync
    VM->>DB: ANY other claims for this client?
    DB-->>VM: No (first claim!)
    VM->>SLACK: NotifyFirstClaimCreated()
    VM-->>UI: return result

Changed Files

1. PortalSlackNotifications.cs

Path: frontend/Claimit.Portal/Notifications/PortalSlackNotifications.cs

New method following the existing notification pattern:

public async Task NotifyFirstClaimCreated(
    string clientName, string courierName, string trackingNumber)
{
    SectionBlock sBlock = new()
    {
        Text = new Markdown(
            $"*First claim created* :tada:\n\n" +
            $"*{clientName}* has created their first claim " +
            $"with courier *{courierName}* " +
            $"(tracking: {trackingNumber}).\n\n" +
            $"Portal: {PortalBaseUrl}")
    };

    await Send("portal-feed", sBlock);
}

2. NewClaimFormViewModel.cs

Path: frontend/Claimit.Portal/Claims/Forms/NewClaimFormViewModel.cs

Added PortalSlackNotifications as a constructor dependency and a new protected method:

protected async Task TryNotifyFirstClaimAsync(SaveClaimResult result)
{
    if (!result.Success) return;

    try
    {
        using var context =
            await databaseContextFactory.CreateDbContextAsync();

        var clientId = result.ClientId;
        var otherClaimExists = await context.Claims
            .AnyAsync(c =>
                c.ClientCourierLogin.ClientId == clientId
                && c.Id != result.Claim!.Id);

        if (!otherClaimExists)
        {
            var client = await clientContext
                .GetCurrentlySelectedClientAsync();
            var courierName =
                SelectedLogin?.Courier?.Name ?? "Unknown";

            await slackNotifications.NotifyFirstClaimCreated(
                client.Name, courierName, result.TrackingNumber);
        }
    }
    catch
    {
        // Notification failure should never affect claim creation
    }
}

Called from SubmitClaim() after a successful save:

var result = await claimFormService.SaveAsClaim(form, ...);
await TryNotifyFirstClaimAsync(result);
return result;

3. ShopifyNewClaimComponent.razor.cs

Constructor updated to pass PortalSlackNotifications through to base. No other changes — inherits SubmitClaim() from base.

4. ZendeskNewClaimComponent.razor.cs

Constructor updated to pass PortalSlackNotifications through to base. SubmitClaim() override updated to call TryNotifyFirstClaimAsync(result) after successful save.

Why this approach?

No Temporal dependency. The notification lives in the portal UI layer (NewClaimFormViewModel), not in the Temporal lifecycle handler. It fires for every courier, whether they use Temporal workflows or not.

Single implementation point. TryNotifyFirstClaimAsync is a protected method on the base view model. Both the base SubmitClaim() and Zendesk's override call it. Shopify inherits the base implementation. No duplication.

DI auto-resolution. The factory uses ActivatorUtilities.CreateInstance which automatically resolves PortalSlackNotifications from the service provider. No factory changes needed.

Silent failure. The bare catch ensures a Slack API issue never prevents the user from creating their claim.