Skip to content

Design: Member-Created Events

Generated by /office-hours on 2026-06-04 Branch: master Repo: rakletadmin/rakletv3 Status: DRAFT — awaiting CEO approval Mode: Startup Author session: gercek@raklet.org


Problem Statement

Today only Raklet admins can create events. A growing share of Raklet customers — federations, hiking clubs, university student-club platforms, volunteer organizations, multi-chapter/multi-location orgs — have distributed event organizers (chapter heads, club presidents, volunteer coordinators, hike leaders, location managers) who run their own events on behalf of the parent brand.

For these orgs, the central admin is structurally unable to be the sole event creator. The current workflow is an out-of-band handoff: the sub-leader emails/WhatsApps event details to the admin, the admin re-keys them into the Raklet admin panel, the event publishes. This handoff is slow, error-prone, and the most cited reason these orgs continue to use Meetup alongside Raklet for member-organized events.

We need a member-portal interface that lets logged-in members submit event proposals (name, description, dates, location, ticket types, capacity, custom fields) directly into Raklet, with mandatory admin approval before the event publishes. Money still routes to the org's payment account; the admin can pick which org account at approval time.

Demand Evidence

  • Hiking club discovery call (current sales cycle). 800-member volunteer org with 50 hike leaders organizing their own events. Customer explicitly described three use cases that all require member-side event creation:
  • Simple event scheduling for 50 hike leaders (currently using Meetup in parallel with Raklet)
  • Paid outing management
  • Lodge booking with approval workflow + fee collection Customer is in 2-week trial. Board recommendation in mid-July. Asked specifically about an "event manager role with limited access." Strongest demand signal in the session.
  • Pattern asked about by multiple customers. Both the sub-leader case (chapter heads, club officers with org-assigned roles) and the regular-member case (members hosting on the org's umbrella) show up in support tickets — different personas, but the submit-with-approval flow serves both.

Status Quo

For distributed-organizer orgs today:

  • Sub-leader emails/WhatsApps event details to admin (typically as a Google Doc or pasted into chat)
  • Admin re-keys into Raklet admin panel
  • Event publishes
  • Money flows to org's default event payment account

For the hiking-club-style case specifically: members use Meetup in parallel with Raklet for member-organized events. We are not replacing nothing; we are replacing Meetup for this workflow.

Target User & Narrowest Wedge

Persona, primary: Sub-leader at a distributed-organizer org (hike leader, chapter head, club president, volunteer coordinator). Already has an explicit role and implicit org authority. Quality-control during approval is "follow the rules," not "are you trustworthy."

Persona, secondary: Regular logged-in member at the same kind of org (e.g., yoga teacher hosting a one-off workshop under the gym's umbrella). Higher abuse surface but same submission flow. Differentiation between personas is deferred to v2 auto-approve rules; in v1 both go through the same mandatory-approval queue.

Narrowest wedge: A member portal form that lets a logged-in member submit a single-occurrence event with all admin-form fields (minus donation ticket type and minus advanced admin-only toggles), with mandatory admin approval before publish, with money defaulting to the org's default event payment account (admin can override the account at approval time).

Constraints

  • Timing pressure: Hiking-club customer makes a board decision mid-July. A credible ship-by date materially affects whether they sign.
  • No new payment routing risk in v1. Members cannot configure bank accounts. Admin can override the default at approval time, but no member-specific routing.
  • Auth gate: Only logged-in members can submit. No anonymous submissions.
  • Single occurrence only in v1. No recurrence rule. Recurring events are explicitly out of scope.
  • No donation-type tickets in member-submitted events. All other ticket types from the admin form are supported.
  • Pre-requisite feature must ship alongside: per-org "default event reminders" setting (see Dependencies).
  • Members can edit their own submissions during pending state. Edit-after-approval rules are an open design question (see Open Questions).
  • Brand/quality risk. Approved events display under the org's brand. Admin approval is the only safeguard against off-brand or low-quality events in v1.

Premises

  1. The wedge segment is distributed-organizer orgs (federations, hiking clubs, multi-chapter, volunteer orgs, student clubs). Validated by hiking-club call.
  2. v1 = single-tier submission with mandatory admin approval. Auto-approve by role or plan tier is v2.
  3. Both personas (sub-leader and regular member) go through the same v1 flow. Differentiation is deferred to v2 via auto-approve rules.
  4. Payment routing is locked to org payment accounts; members see no bank-account UI. Admin can select bank account at approval time using the existing PaymentAccountId field.
  5. Member submission form matches the admin event form's fields (not its full advanced-toggle feature set). Skip: custom URL slug, social-share imagery, embed widgets, donation tickets. Keep: name, description, dates, location, ticket types, capacity, custom fields, cover image.
  6. Edit is in v1 (both during pending and after approval, scope TBD). This adds queue-state complexity — see Open Questions.
  7. "Default event reminders" ships as a separate org-level site feature alongside this. Without it, member-created events would have no reminders, breaking the UX. Admin-created events also use the default (with override).
  8. Notifications, tiered by urgency in v1:
  9. Admin email = daily digest (events are not time-urgent; reduces inbox noise; 24h SLO is acceptable because the member is not waiting in the dark — see Premise #8a).
  10. Admin in-app notification center = real-time (bell counter updates immediately on submission, no spam cost).
  11. Submitter email on approval / rejection = real-time (this is the member's actual wait moment — they should know fast).
  12. Per-manager opt-in via existing OrganisationManagerSettingsDoc.ManagerEmailSettings scaffolding (default ON for the digest).
  13. Granular per-admin frequency choice (instant vs daily vs weekly vs off) = v1.5.

8a. The anti-anxiety mechanism for the member is the "My Submissions" view, not email speed. As long as the member sees their submission as "Pending" immediately after submit, they're not in the dark. This decouples admin response cadence from member experience. 9. Approval-queue infrastructure clones the membership-application pattern (Application/Controllers/ApplyController.cs:1946-1958) — proven, deployed, low-novelty.

Approaches Considered

Approach A: Status field on EventDoc (minimal-viable)

Add SubmissionStatus enum + SubmittedByMemberId + SubmittedAt fields to existing EventDoc. Member submission creates an EventDoc directly with IsPublished=false, SubmissionStatus=Pending. Admin approval flips IsPublished=true + SubmissionStatus=Approved. Existing event queries already filter on IsPublished, so pending events are invisible everywhere except a new "Pending" tab in the admin events list.

  • Effort: ~4 weeks (human team) / proportional CC time
  • Risk: Low. Minimal model changes. Zero model drift.
  • Reuses: EventDoc, EventDetailsEditViewModel, V2EventsController create/update flows, PaymentAccountId field, ManagerEmailSettings scaffolding.
  • Pros: Fastest ship. Single source of truth. Edit-after-approval is just normal event editing.
  • Cons: Pending events live in the main events Cosmos collection. Any future code path that scans EventDoc must filter on SubmissionStatus. One missed filter = leak. Admin "Pending" tab is buried in the events list — not the strong queue UX the hiking-club use case warrants.

Approach B: Separate EventSubmissionDoc model (textbook clean)

Net-new EventSubmissionDoc Cosmos model mirroring ApplicationDoc. Member fills form → creates EventSubmissionDoc. Admin reviews on a dedicated "Event Submissions" admin page (separate from events list). On approval, server converts the submission into a real EventDoc and links them via SourceSubmissionId. Rejected submissions remain as EventSubmissionDoc with Status=Rejected.

  • Effort: ~7-8 weeks (human team)
  • Risk: Medium. Model drift over time — every EventDoc field addition forces an EventSubmissionDoc change to match. Edit-after-approval semantics ambiguous (mutate EventDoc directly? push back through submission again?).
  • Reuses: ApplyController.cs:1946-1958 approval pattern, email-template-keys naming convention.
  • Pros: Cleanest mental model. Zero leak risk. Matches proven Application pattern exactly.
  • Cons: Slow. Double model maintenance forever. Edit-after-approval is genuinely ambiguous and forces a UX call we don't have to make in A or C.

Same backend data model as A — single EventDoc with SubmissionStatus enum. But dedicated UI surfaces like B: a separate "Pending Submissions" admin page (not a tab buried in the events list) and a "My Submissions" view in the member portal that shows status (Pending / Approved / Rejected) for each submission. Status flag drives visibility filters consistently; UI is purpose-built for the submission lifecycle.

  • Effort: ~5-6 weeks (human team)
  • Risk: Low-medium. Single model, no drift. "Remember to filter on SubmissionStatus" discipline is real — mitigated with a helper accessor (EventQueryHelpers.PublishedOnly()) that becomes the standard.
  • Reuses: Everything Approach A reuses, plus the Application approval-queue pattern for the admin page layout (ApplyController flow).
  • Pros: Approach A's velocity with most of Approach B's UX clarity. Hiking-club admin gets a real queue, not "did you remember the pending tab?" Member's "My Submissions" view is the surface that handles the "don't keep members waiting" UX worry (transparency, status badges, reason on rejection).
  • Cons: Two new UI surfaces (admin pending page + member my-submissions view) instead of one tab. Filter discipline is enforceable in code review but not in the type system.

Approach C.

Rationale (one line per reason): - Approach B's model-drift tax is genuinely expensive given how often EventDoc gains fields. - Approach A's "tab in the events list" UX is not the queue experience the hiking-club admin needs. - C buys A's velocity + most of B's UX clarity in the middle of the effort range. - If timing for the hiking-club mid-July deadline becomes tight, the backend in C is identical to A — we can ship A's UI first (tab in events list), then UI-upgrade to dedicated pages in a follow-up sprint without any backend or data migration.

v1 architecture sketch

Note: the data model below is the original office-hours sketch. It was superseded during the engineering review by the two-status model (codex #1 fix). See the Engineering Review Decisions → Edit-after-approval data model section below for the authoritative current model.

EventDoc (existing, +3 fields) -- SUPERSEDED, see Engineering Review Decisions
  ├── SubmissionStatus: enum { NotSubmitted, Pending, Approved, Rejected }
  ├── SubmittedByMemberId: Guid?
  ├── SubmittedAt: DateTime?
  └── (existing fields unchanged)

NEW endpoint: POST /api/app/events/submissions
  - Auth: must be logged-in member of the org
  - Body: member-form fields (subset of EventDetailsEditViewModel)
  - Side effects:
      - Creates EventDoc with IsPublished=false, SubmissionStatus=Pending
      - Posts in-app notification to admin notification center (real-time)
      - Enqueues admin daily-digest entry (no immediate email)
      - Submitter sees "Pending" in My Submissions view immediately

NEW endpoint: PATCH /api/app/events/submissions/{id}
  - Edit while Pending (allowed)
  - Edit while Approved (allowed; admin re-notified — UX decision in Open Questions)

NEW endpoint: POST /api/v2/events/{id}/approve
  - Auth: admin with "Manage Events" permission
  - Body: { PaymentAccountId } — admin can override default
  - Side effects:
      - Sets SubmissionStatus=Approved, IsPublished=true
      - Sends approval email to submitter
      - In-app notification to submitter

NEW endpoint: POST /api/v2/events/{id}/reject
  - Auth: admin with "Manage Events" permission
  - Body: { ReasonNote }
  - Side effects:
      - Sets SubmissionStatus=Rejected, IsPublished=false
      - Sends rejection email to submitter (with reason)

NEW Angular template: app-side submission form
  - File: Raklet.Backend/Content/scripts/app/events/templates/event-submit.html
  - Or in member-portal codebase (location TBD via /plan-eng-review)
  - Field subset of event-upsert.html minus admin-only fields

UPDATED Angular templates: admin events list + event detail show member submission inline
  - File: Raklet.Backend/Content/scripts/core/manager/events/templates/events-list.html (existing, add badge + DraftStatus indicator on rows)
  - File: Raklet.Backend/Content/scripts/core/manager/events/templates/event-upsert.html (existing, add approval panel + bank-account dropdown when DraftStatus=Pending)
  - No new tab. Pending member-submitted events appear in the existing admin events list with a "Submitted by [Member]" badge and the standard event-row controls.
  - Admin opens any event with DraftStatus=Pending → existing event detail page renders the approval panel inline (read-only preview of PendingDraft fields + bank-account dropdown defaulted to org's default + Approve/Reject buttons).
  - DECISION (eng review override): no separate tab; pending events live in the same list with admin-created events.
  - **Bulk-action audit required:** every admin-list action (bulk publish, export, send invites, filter, reminder toggle) gated on `DraftStatus != Pending` or explicitly opted in. See codex #6 finding folded into Engineering Review Decisions below.

NEW Angular template: member "My Submissions" view
  - File: Raklet.Backend/Content/scripts/app/events/templates/my-submissions.html
  - List of submissions with status badges
  - Edit affordance for Pending/Approved (per Open Question resolution)

NEW org settings:
  - OrganisationManagerSettingsDoc.ManagerEmailSettings.PendingEventSubmissionEmails (per-manager opt-in, default true)
  - OrganisationSettingsDoc.Settings["Events-PendingSubmissionTemplateId"] (email template)
  - OrganisationSettingsDoc.Settings["Events-ApprovedTemplateId"] (email template)
  - OrganisationSettingsDoc.Settings["Events-RejectedTemplateId"] (email template)
  - OrganisationSettingsDoc.Settings["Events-DefaultReminderTemplates"] (pre-req feature)

Open Questions

  1. Edit-after-approval semantics. POLICY LOCKED, DATA MODEL DEFERRED to /plan-eng-review (per CEO review + codex cross-model challenge): Member opens an approved event → enters edit mode on a draft copy. Member clicks "Save" to persist edits without queueing re-approval (public event continues to show last-approved version). Member clicks "Submit for approval" to explicitly drop the event to Pending and queue admin re-review. No silent re-approval triggers on save. Public event always shows the last-approved version until admin re-approves the new version. Admin sees a "draft pending review" indicator; attendees see no indicator. Pattern is GitHub-PR-draft applied to event editing.

Codex flagged this as a hidden versioning model inside Approach C — it implies "currently approved" version + "member's draft" version + a transition. /plan-eng-review must explicitly model this (options: A) two field-sets on the same EventDoc with a DraftSubmissionStatus enum; B) separate sub-doc / linked draft EventDoc; C) cut the draft pattern entirely and fall back to "member contacts admin to edit"). The product policy holds either way — the question is which data model implements it without leaking pending content into the wrong queries. 2. Cover image upload. Admin-form supports it. Should member submissions in v1? Adds blob storage + moderation surface. Default proposal: yes, single cover image, same moderation as text — admin sees it during approval. 3. Member-portal location. Submission form: top-level "Submit an Event" link in member portal navigation, or embedded under existing Events tab? Likely the latter, but needs UX call. 4. Custom fields strategy. Admin form has org-configurable custom fields per event. For v1: do we expose all of them to the member form, or curate a v1 subset? Most likely subset (skip advanced custom-field types). 5. Reject reason visibility. Is the reject reason visible to the submitter? Almost certainly yes — but is it a required field at reject time? 6. Rate limiting. Member submission endpoint needs spam protection. Per-member daily cap? Per-org cap? 7. 90-day success number. RESOLVED by CEO review: 300 events approved across all paying orgs. Kill signal: fewer than 2 paying orgs use feature by day 60 — pause v1.5, do customer research.

  1. Approval-queue SLA + escalation. RESOLVED by CEO review: 7-day SLA with 2-stage escalation. At 3 days pending: email reminder to all admins + bell counter persists. At 7 days pending: escalate to org owner + show "attention needed" badge in admin panel. Member sees "Submitted N days ago" progress text on their My Submissions view from day 1. Prevents the "pending forever" silent-failure mode. Adds ~1 day CC scope.

  2. Mobile-first posture. PARTIALLY RESOLVED: Member surfaces (submission form + My Submissions view) follow Raklet's existing member-portal responsive convention; /plan-design-review verifies this with real mockups against the hike-leader-on-phone use case. Admin "Pending" tab follows existing admin desktop-first convention.

Cross-Model Perspective

(Second opinion was not run in this session — skipped to preserve velocity. Recommend running /plan-ceo-review next, which will provide cross-model challenge of the scope and wedge calls before /plan-eng-review locks the architecture.)

Success Criteria

Launch criteria (gate to opening up to paying customers): - Hiking-club trial customer can submit at least 3 events through the member flow and have them approved in production. - Per-org default reminders setting ships and applies correctly to both admin-created and member-created events. - Admin approval flow shipped with bank-account-override at approval time. - Per-manager email opt-in working with default ON.

90-day post-launch success — REVISED by CEO review after cross-model challenge from codex:

The original "X events approved across all paying orgs" framing was a vanity proxy. Real business goal is converting the hiking club + proving the distributed-organizer wedge. Replaced with a 4-metric stack:

  1. Hiking-club customer converted to paying + active by day 60. Primary success signal.
  2. At least 3 other paying orgs have a member submit at least once. Proves the wedge isn't a one-customer feature.
  3. Repeat-leader ratio >50% in adopting orgs. Proves the workflow sticks — leaders submitting once and never again means we built submission but missed the actual job.
  4. At least 1 customer explicitly reports reduced Meetup usage. Direct evidence of Meetup displacement (the original status-quo replacement claim).

Counter-metric (kill signal): - Fewer than 2 paying orgs use the feature by day 60, OR hiking-club doesn't convert OR drops Raklet after trial → wedge is wrong, pause v1.5, do customer research.

Distribution Plan

Standard Raklet deployment pipeline: feature ships via dev → test → prod branch promotions per existing release process (see reference_master_test_prod_promotion.md). EF migrations for the EventDoc Cosmos model additions go in their own branch and ship before logic-code branches per existing convention (feedback_migration_branch_first.md). No new distribution channels needed — this is in-product UI.

Dependencies

  • PRE-REQUISITE FEATURE: Per-org "default event reminders" setting. Must ship before or alongside member-created events. New OrganisationSettingsDoc.Settings["Events-DefaultReminderTemplates"] key. Admin UI to configure. Both admin-created and member-created events inherit. Estimated ~1 week on its own.
  • Approval queue pattern: existing in ApplyController.cs:1946-1958 — clone, don't reinvent.
  • Per-manager email scaffolding: existing in OrganisationManagerSettingsDoc.ManagerEmailSettings — add one new opt-in key.
  • Payment routing: existing PaymentAccountId + DefaultPaymentAccountId patterns — reuse.
  • New email templates (3): EventSubmissionReceived, EventSubmissionApproved, EventSubmissionRejected. Same pattern as ApplicationReceived / ApplicationApproved.
  • Member portal Angular surface must exist for new pages (verify via /plan-eng-review where this code lives — Raklet.Backend/Content/scripts/app/ or a separate member-portal codebase).

The Assignment

Schedule a 30-minute call with the hiking-club prospect this week.

Not "build the feature." Not "validate the design." A real conversation. Specifically:

  • Walk them through the v1 scope from this design doc (the Lean Scope table above).
  • Ask: "If this shipped exactly as I just described, would it replace your Meetup usage entirely, or only partially? What would still send your hike leaders back to Meetup?"
  • Ask: "What's the smallest version of this that would change your board recommendation in mid-July?"
  • Listen for what they say is missing that you don't currently plan to build. That's the v1 scope conversation we should have before the eng team starts.

The signal you're listening for: do they say "yes, that solves it" or do they say "but what about X" — and is X already in our plan, or is X something we'd cut?

This call has to happen before /plan-eng-review locks the implementation, because the mid-July deadline will not survive a scope surprise discovered in week 4 of build.

What I noticed about how you think

A few things from how this session went that are worth naming back to you:

  • You wrote the spec like a PM, not a CEO. Your opening message had three layered requirements (UI, approval, payment routing) and surfaced the "site feature for default reminders" gotcha unprompted. That's an unusually clean product brief. Most CEOs hand off "let members create events" and leave the reminders surprise to engineering.
  • You pushed back on "what's the difference?" when I distinguished sub-leader vs. regular-member personas — instead of nodding. That question is the reason this design is right. If you'd just accepted the distinction, we'd be building a two-tier system in v1 instead of a single flow with v2 auto-approve. The instinct to ask "but why?" when an AI sounds confident is worth keeping.
  • You changed direction on notification timing mid-session. You agreed to "daily digest," then reconsidered and pushed to real-time. That's not flip-flopping; that's holding the user experience as the load-bearing concern over engineering convenience. Worth doing more of, even when I argue against you.
  • You asked me to check the codebase. This is the move that separates the people who get useful designs from AI from those who get plausible-sounding ones. The codebase research is what changed "per-manager opt-in is v1.5" to "per-manager opt-in is free in v1." That single finding probably saved a quarter of cycle time on this feature.

Engineering Review Decisions (locked by /plan-eng-review on 2026-06-05)

Architecture

  • Member-portal surface (D2). Code lives at Application/Areas/App2/Controllers/EventsController.cs + Application/Areas/App2/Views/Events/. Matches the existing area pattern (Profile, Membership, Payments). Razor MVC with embedded AngularJS widgets where needed for the ticket-types editor.

  • EventDoc leak-risk filter (D3 + codex #2). Centralized via per-surface explicit helpers, deny-by-default:

  • EventQueryFilters.VisibleForPublic()IsPublished=true AND PublishStatus=Published
  • EventQueryFilters.VisibleForMember(memberOrgId) → same as Public, scoped to org
  • EventQueryFilters.VisibleForAdmin(adminOrgId, permissions) → all events in org including drafts/pending/rejected
  • EventQueryFilters.VisibleForSystemJob() → explicit caller — each job declares whether it wants approved-only or all
  • ALL 24 existing EventDoc-query callsites refactored to use the appropriate helper
  • Boundary audit (codex #3) extends scope: data-flow inventory of every EventDoc-emitting boundary (iCal calendar feeds, exports, Zapier integration, search indexes, email templates) with leak regression tests at each boundary, not just the query layer
  • Admin-list bulk-action audit (codex #6): every admin-list action (bulk publish, export, send invites, filter, reminder toggle) audited and gated by DraftStatus before shipping the embedded UI

  • Edit-after-approval data model (D4 + D6 fix from codex #1). Two-status model on EventDoc:

    EventDoc {
      // Approved fields (publicly visible)
      Title, Description, StartDate, TicketTypes, PaymentAccountId, ...
      IsPublished, PublishStatus: enum { Draft, Published, Archived },
    
      // Draft review state (independent of publish state)
      DraftStatus: enum { None, Pending, Rejected },
      DraftCreatedAt: DateTime?,
    
      // Submission metadata
      SubmittedByMemberId: Guid?,
      SubmittedAt: DateTime?,
    
      // Member's pending edits (allowlisted fields only)
      PendingDraft: {
        Title, Description, StartDate, Capacity, CoverImageUrl,
        // NOT: PaymentAccountId, TicketTypes prices, IsPublished, PublishStatus
        SubmittedAt: DateTime?,
        DraftCreatedAt: DateTime?
      }
    }
    

  • Public visibility = PublishStatus = Published (ignores DraftStatus entirely). Public never sees an approved event disappear.
  • Admin sees pending badge when DraftStatus = Pending.
  • First-time submission: PublishStatus = Draft AND DraftStatus = Pending. Approval flips to Published + None.
  • Edit-to-approved submission: PublishStatus = Published AND DraftStatus = Pending. Approval merges PendingDraft allowlisted fields into top-level fields; clears PendingDraft.
  • SLA escalation reads DraftStatus = Pending AND DraftCreatedAt < N days ago — no conflation between first-submission and edit-to-approved (closes codex #9).
  • Field-level allowlist on PendingDraft (codex #4): financial fields (PaymentAccountId, TicketTypes prices) cannot be changed by member edit. Member can change capacity, description, dates, cover image. Admin can change anything during approval.
  • ETag/version check on approve to prevent stale-edit overwrites.

  • Admin UI is the existing events list (user override during eng review). No separate "Pending" tab. Pending submissions appear inline with a "Submitted by [Member Name] on [date], awaiting approval" badge. Approval panel embedded in the existing event detail page when DraftStatus = Pending. Same admin pages used for member-created and admin-created events.

  • Default reminders (user override during eng review). Hardcoded 24h-before reminder for member-submitted events. Org-configurable default-reminders feature deferred entirely (was originally a pre-req). Edge-case suppression rules (codex #7): events created <24h before start fire immediately or skip per a deterministic rule; reminder math runs in the event's timezone, not server time; canceled/rejected events don't fire.

  • Feature flag wrap (1.5). Entire member submission flow behind a feature flag. Flag-off = members can't submit, existing event functionality unchanged. Rollback without code revert.

Validation

  • Shared validators + member-specific command model (D5 + codex #5). Validation attributes/rules live in shared validators reused by admin form and member form. Member submission uses a MemberEventSubmissionCommand DTO that's narrower than EventDetailsEditViewModel — member-only fields. Field-level authorization prevents member from sending admin-only fields like custom URL slug.

Background jobs

  • Idempotency for digest + SLA jobs (codex #8). Both jobs persist recipient-level send markers keyed by org + event + threshold + recipient + date. Run through Raklet's existing lease/checkpoint pattern for distributed locking. No duplicate emails on retry.

  • Daily digest runs once per day per org. Sends per-admin filtered by OrganisationManagerSettingsDoc.ManagerEmailSettings.PendingEventSubmissionEmails (default ON). Empty queue day = no email.

  • SLA escalation at 3 days (admin reminder) and 7 days (org owner escalation). Backfill missed days on startup after outage. Alert if job not run in 25h.

Design Review Decisions (locked by /plan-design-review on 2026-06-05)

Visual design system: Reuse Raklet's existing Bootstrap 4 + FontAwesome + Social* CSS namespace conventions. No new design vocabulary. All user-facing strings in Raklet.Admin/Content/locales/manager/*.json across all 5 locales (de, en, fr, sv, zh).

Member submission form (D2): field order optimized for member task flow 1. Event title (text input) 2. When — start date + start time + end date + end time (HTML5 native pickers on mobile) 3. Where — location/venue field 4. Capacity (number input) 5. Ticket types (single paid + free; reuses admin ticket subentity editor) 6. Description (rich text or textarea) 7. Cover image (optional, upload at bottom)

Rationale: matches how a hike leader actually thinks about an event — "what / when / where / who / how / details." Different visual order from admin form; same shared validators behind. Reordering is free (different template, same DTO).

Member My Submissions empty state (D3): designed first-touch experience - Centered Bootstrap card with FontAwesome fa-calendar-plus icon - Headline: "Host an event for [Org Name]" - Body: "Submit your event details for admin review. Once approved, your event publishes under [Org Name]'s brand." - Primary button: btn btn-primary btn-lg "+ Submit your first event" - Secondary text link: "How does approval work?" → optional FAQ modal (deferred to v1.5 if not present) - All strings live in locale files; English version above is canonical

Rationale: this is the conversion surface for the 90-day success metric (300 events, hiking-club adoption). The default badge badge-danger NoRecords pattern from Profile/Index.cshtml:73-76 is hostile here.

Mobile-first responsive for member surfaces (D4) - Single column always (no col-md-6 side-by-side, even on desktop, for the submission form) - Native HTML5 inputs for date/time/number (mobile keyboards handle these) - Sticky bottom action bar containing Save + Submit-for-approval buttons (always visible on phone without scroll) - 44px minimum touch target for all interactive elements (Bootstrap btn-lg ≈ 48px) - Helper text collapsible behind a fa fa-info-circle tooltip on screens < 576px (Bootstrap sm breakpoint) - Admin "Pending" badge + approval panel: stays desktop-first per existing Raklet.Backend convention; works on mobile but not optimized

Admin events list pending badge + approval flow (D5) - Pending events show a prominent badge badge-warning with fa-clock icon in the status column of the existing events list row - Badge label: "Pending Approval" (or i18n equivalent) - Hover tooltip: "Submitted by [Member Name] on [date]" - No inline Approve/Reject buttons on the row — admin must click into the event detail page - Approval panel renders inline in the existing event detail page when DraftStatus = Pending: - Read-only preview of PendingDraft fields (or top-level fields if no draft yet) - "Submitted by [Member] on [date]" header - PaymentAccountId dropdown (defaulted to org default) - Primary button: btn btn-success "Approve" - Secondary button: btn btn-danger "Reject" (opens reason textarea) - Rationale: forces admin to read context before deciding — protects against accidental approval of lodge-booking submissions that need deliberate review (codex CEO-review finding)

Member edit form button hierarchy (D6) - Primary button: btn btn-primary "Save" (saves draft, no admin queue action, public event unchanged) - Secondary button: btn btn-default "Submit for approval" (drops event to DraftStatus=Pending, public continues to show last-approved version) - Pattern matches the "GitHub PR draft" mental model locked in eng review (D4) - Save encourages iteration; Submit is the explicit deliberate action

Interaction states (covered by D3 + D5):

Surface Loading Empty Error Success Partial
Submission form Submit button shows spinner, disabled N/A (form always populated) Inline field validation + form-level alert Redirect to My Submissions with growl confirmation (none — atomic submit)
My Submissions Loading skeleton rows Designed empty state (D3) Form-level alert with retry Status badges per row Pagination at 25/page
Edit form (pending) Save/Submit spinner N/A Inline + form-level Growl confirmation; status badge updates (none — atomic)
Edit form (approved with draft) Same as pending N/A Same as pending Same; public event reflects approved version "Draft pending review" indicator for admin
Admin events list Existing pattern Existing pattern Existing pattern Existing pattern Pagination existing
Admin approval panel Approve/Reject spinner N/A Form-level alert Growl + panel disappears (none — atomic flip)

Accessibility floor (universal): - All form fields have visible labels (no placeholder-as-label) - All error messages programmatically associated with their input via aria-describedby - Keyboard navigation: tab order matches visual order; all interactive elements focusable - Color contrast WCAG-AA (4.5:1 body text; existing Raklet badge colors verified by /design-review post-impl) - Screen-reader-only labels (sr-only class) for icon-only buttons

Test scope

  • All 24 EventDoc-query callsites get integration regression tests (D7). Per feedback_no_test_only_di_seams.md, integration tests against real EventDoc store, not Moq.
  • Negative tenant-isolation tests (codex #10): assert pending events of Org A NEVER appear in any query scoped to Org B, even with malformed input.
  • Boundary-output regression tests (codex #3): calendar feeds, exports, Zapier, search, email templates all get pending-leak negative tests.
  • 3 E2E browse-test flows: double-click submission idempotency, admin approve flow on existing events list, SLA escalation timing.
  • Full test plan: member-created-events-test-plan.md.

Next steps

  1. Run the assignment — call the hiking-club prospect, validate scope.
  2. /plan-eng-review — lock architecture, edge cases, test coverage. Resolve the 5 engineering open questions above + the 7 from earlier. Surface the data-migration plan for the new EventDoc fields. Decide where the member-portal Angular code lives.
  3. /plan-design-review — visual + UX critique of the three new surfaces (member submission form, member "My Submissions" view, admin pending tab + approval modal).