diff --git a/.github/workflows/workflows-queue-consistency.yaml b/.github/workflows/workflows-queue-consistency.yaml
new file mode 100644
index 0000000..8ba5c9b
--- /dev/null
+++ b/.github/workflows/workflows-queue-consistency.yaml
@@ -0,0 +1,28 @@
+name: Workflows queue consistency
+
+# Fails the build if the analysis producer, the workflows engine and the stage
+# workers would render onto different WORKFLOWS_QUEUE names — the silent
+# producer/consumer queue-name drift that leaves runs piling up with no
+# consumer. Pure `helm template` render check, no cluster required.
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - 'charts/hub/**'
+ - 'scripts/check-workflows-queue-consistency.sh'
+ - '.github/workflows/workflows-queue-consistency.yaml'
+
+jobs:
+ check:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Helm
+ uses: azure/setup-helm@v4
+ with:
+ version: v3.16.2
+
+ - name: Check WORKFLOWS_QUEUE consistency
+ run: ./scripts/check-workflows-queue-consistency.sh charts/hub
diff --git a/charts/hub/custom-layout/templates/share_case_otp.html b/charts/hub/custom-layout/templates/share_case_otp.html
new file mode 100644
index 0000000..52c0dbf
--- /dev/null
+++ b/charts/hub/custom-layout/templates/share_case_otp.html
@@ -0,0 +1,425 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ Verify your access
+ Use the code below to open the shared case
+ |
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+ |
+
+ Your verification code
+ Enter the code below on the share page to access the case.
+
+ {{code}}
+
+ This code expires in {{expiry}}. If you didn't request this, you can safely ignore this email.
+ |
+ |
+
+
+
+ |
+
+
+
+
+
diff --git a/charts/hub/custom-layout/templates/share_case_otp.txt b/charts/hub/custom-layout/templates/share_case_otp.txt
new file mode 100644
index 0000000..46b46b0
--- /dev/null
+++ b/charts/hub/custom-layout/templates/share_case_otp.txt
@@ -0,0 +1,21 @@
+Kerberos.io
+------------
+
+Verify your access
+Use the code below to open the shared case
+
+Your verification code
+{{code}}
+
+Enter this code on the share page to access the case. This code expires in {{expiry}}.
+If you didn't request this, you can safely ignore this email.
+
+Get in touch
+------------
+support@kerberos.io
+9000 Ghent, BE
+https://kerberos.io
+
+About Kerberos
+------------
+Welcome to the revolutionary video analytics and video management platform. Open, modular, and extensible for everyone, anywhere.
diff --git a/charts/hub/templates/kerberos-hub/hub-api.yaml b/charts/hub/templates/kerberos-hub/hub-api.yaml
index 43997b2..59bfc48 100644
--- a/charts/hub/templates/kerberos-hub/hub-api.yaml
+++ b/charts/hub/templates/kerberos-hub/hub-api.yaml
@@ -357,6 +357,10 @@ spec:
value: "{{ .Values.email.templates.caseShare }}"
- name: CASE_SHARE_TITLE
value: "{{ .Values.email.templates.caseShareTitle }}"
+ - name: CASE_SHARE_OTP_TEMPLATE
+ value: "{{ .Values.email.templates.caseShareOtp }}"
+ - name: CASE_SHARE_OTP_TITLE
+ value: "{{ .Values.email.templates.caseShareOtpTitle }}"
- name: ASSIGN_TASK_TEMPLATE
value: "{{ .Values.email.templates.assignTask }}"
- name: ASSIGN_TASK_TITLE
diff --git a/charts/hub/values.yaml b/charts/hub/values.yaml
index 6d95f3a..05b579c 100644
--- a/charts/hub/values.yaml
+++ b/charts/hub/values.yaml
@@ -1121,6 +1121,8 @@ email:
shareTitle: "[Action] You received a recording from Kerberos Hub"
caseShare: "share_case"
caseShareTitle: "[Action] A case has been shared with you on Kerberos Hub"
+ caseShareOtp: "share_case_otp"
+ caseShareOtpTitle: "Your Kerberos Hub verification code"
assignTask: "assign_task"
assignTaskTitle: "[Action] You've been assigned to a task"
detection: "detection"
diff --git a/scripts/check-workflows-queue-consistency.sh b/scripts/check-workflows-queue-consistency.sh
new file mode 100755
index 0000000..687d7a6
--- /dev/null
+++ b/scripts/check-workflows-queue-consistency.sh
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+#
+# Render the hub chart and assert that every deployment which carries the
+# workflows hand-off queue (the WORKFLOWS_QUEUE env var) resolves to the SAME,
+# non-empty value.
+#
+# Why: the analysis pipeline (pipe-analysis) publishes opened workflow runs to
+# WORKFLOWS_QUEUE, the workflows engine (hub-workflows) consumes it, and every
+# stage worker (hub-stage) routes its result back to it. All three templates
+# read the single key `kerberoshub.services.workflows.queue`. If a future edit
+# hardcodes a value, reads the wrong key, or drops the env on one of them, the
+# producer and consumer silently drift onto different queue names and messages
+# pile up with no consumer. This check fails the build before that can ship.
+#
+# Usage: scripts/check-workflows-queue-consistency.sh [chart-dir]
+# (chart-dir defaults to charts/hub, relative to the repo root)
+
+set -euo pipefail
+
+CHART_DIR="${1:-charts/hub}"
+PROBE="drift-probe-queue-name"
+
+# Flags that force all three deployment kinds (analysis, engine and one stage
+# worker) to render, so the check actually has something to compare. anpr is a
+# stage/worker shipped in the chart's default values.
+RENDER_FLAGS=(
+ --set mode=all
+ --set kerberoshub.workflows.enabled=true
+ --set kerberoshub.workflows.stages.anpr.enabled=true
+ --set kerberoshub.services.anpr.enabled=true
+)
+
+# Read `helm template` output on stdin and print one WORKFLOWS_QUEUE value per
+# line. Matches the `- name: WORKFLOWS_QUEUE` env entry and captures the value
+# from the following `value:` line, skipping blank/comment lines in between.
+extract_workflows_queue() {
+ awk '
+ /^[[:space:]]*-[[:space:]]*name:[[:space:]]*WORKFLOWS_QUEUE[[:space:]]*$/ { want=1; next }
+ want==1 {
+ if ($0 ~ /^[[:space:]]*#/ || $0 ~ /^[[:space:]]*$/) next
+ v=$0
+ sub(/^[[:space:]]*value:[[:space:]]*/, "", v)
+ sub(/^"/, "", v); sub(/"[[:space:]]*$/, "", v)
+ sub(/[[:space:]]+$/, "", v)
+ print v
+ want=0
+ }
+ '
+}
+
+assert_all_equal() {
+ local expected="$1"; shift
+ local label="$1"; shift
+ local -a vals=("$@")
+
+ if [ "${#vals[@]}" -lt 2 ]; then
+ echo "FAIL (${label}): expected at least 2 WORKFLOWS_QUEUE values (analysis + engine), found ${#vals[@]}" >&2
+ return 1
+ fi
+
+ local v
+ for v in "${vals[@]}"; do
+ if [ -z "${v}" ]; then
+ echo "FAIL (${label}): a deployment rendered an empty WORKFLOWS_QUEUE value" >&2
+ return 1
+ fi
+ if [ "${v}" != "${expected}" ]; then
+ echo "FAIL (${label}): WORKFLOWS_QUEUE drift detected — expected '${expected}' but a deployment rendered '${v}'" >&2
+ printf ' rendered values: %s\n' "${vals[*]}" >&2
+ return 1
+ fi
+ done
+
+ echo "OK (${label}): ${#vals[@]} deployments all use WORKFLOWS_QUEUE='${expected}'"
+}
+
+echo "== Rendering ${CHART_DIR} with the chart's default workflows queue =="
+default_out="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}")"
+mapfile -t default_vals < <(printf '%s\n' "${default_out}" | extract_workflows_queue)
+default_queue="${default_vals[0]:-}"
+assert_all_equal "${default_queue}" "default values" "${default_vals[@]}" || exit 1
+
+echo "== Rendering ${CHART_DIR} with an overridden workflows queue (-> ${PROBE}) =="
+probe_out="$(helm template hub "${CHART_DIR}" "${RENDER_FLAGS[@]}" \
+ --set kerberoshub.services.workflows.queue="${PROBE}")"
+mapfile -t probe_vals < <(printf '%s\n' "${probe_out}" | extract_workflows_queue)
+assert_all_equal "${PROBE}" "override probe" "${probe_vals[@]}" || exit 1
+
+echo "All WORKFLOWS_QUEUE consistency checks passed."