Portal Community

What the Pre-Send Hook Can Do

Blocking an Interaction

public class OutOfOfficeRedirectHook : IInteractionHook
{
    private readonly IUserPresenceService _presence;

    public OutOfOfficeRedirectHook(IUserPresenceService presence)
    {
        _presence = presence;
    }

    public async Task OnBeforePublish(
        InteractionRequest request,
        CancellationToken ct)
    {
        var userStatus = await _presence.GetStatusAsync(request.TargetUserId, ct);

        if (userStatus == UserStatus.OutOfOffice)
        {
            var substitute = await _presence.GetSubstituteAsync(request.TargetUserId, ct);

            if (substitute is null)
            {
                // No substitute — block the interaction
                throw new InteractionBlockedException(
                    $"User {request.TargetUserId} is out of office and has no substitute.");
            }

            // Redirect to substitute
            request = request with { TargetUserId = substitute.UserId };
        }
    }

    public Task OnAfterRespond(
        InteractionRequest request,
        InteractionResponse response,
        CancellationToken ct) => Task.CompletedTask;
}

Enriching the Request

public class TraceEnrichmentHook : IInteractionHook
{
    private readonly ITraceContext _trace;

    public async Task OnBeforePublish(
        InteractionRequest request,
        CancellationToken ct)
    {
        // Add OpenTelemetry trace context to metadata
        request.Metadata["traceparent"] = _trace.CurrentTraceParent;
        request.Metadata["tracestate"]  = _trace.CurrentTraceState;
        request.Metadata["publishedBy"] = _trace.CurrentService;
        await Task.CompletedTask;
    }

    public Task OnAfterRespond(
        InteractionRequest request,
        InteractionResponse response,
        CancellationToken ct) => Task.CompletedTask;
}

InteractionBlockedException

PropertyTypeDescription
MessagestringHuman-readable reason for blocking
BlockedBystringName of the hook that blocked (auto-populated)
InteractionIdstringThe interaction that was blocked

When InteractionBlockedException is thrown, PublishAndWaitAsync() re-throws it to the caller. The interaction is recorded in the audit log as status: "blocked".

Modifying the Request Object The InteractionRequest passed to OnBeforePublish is mutable during the hook chain. Modifications made in one hook are visible to subsequent hooks and to the delivery stage. Be careful about ordering when multiple hooks modify the same field.

Pre-Send Hook Use Cases

Use CaseImplementation
Out-of-office redirectCheck user presence; change TargetUserId to substitute
Rate limitingCount interactions per user per minute; throw if exceeded
Payload enrichmentAdd dynamic fields to payload from external data sources
Spam protectionDetect duplicate interactions (same type + target within N seconds)
Trace context injectionAdd OTel traceparent to metadata for distributed tracing
Permission checkVerify the requester has permission to send this type to this user