First Claim Slack Notification
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:#e1f5feAll 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 resultChanged 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.
TryNotifyFirstClaimAsyncis a protected method on the base view model. Both the baseSubmitClaim()and Zendesk's override call it. Shopify inherits the base implementation. No duplication.
DI auto-resolution. The factory uses
ActivatorUtilities.CreateInstancewhich automatically resolvesPortalSlackNotificationsfrom the service provider. No factory changes needed.
Silent failure. The bare
catchensures a Slack API issue never prevents the user from creating their claim.