> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# User Management

> A central place to manage users, groups, and information about them.

## Post Email Delivery Recovery

The post email queue now treats provider-accepted delivery as durable once it is written to `email_audit`. The `sent_emails` table is still the primary history surface for the product UI, but queue retries must not resend a post email just because the follow-up `sent_emails` insert or `user_group_post_checks.email_id` link failed.

### Recovery Rules

* `email_audit` is created before a send and updated to `sent` immediately after provider success.
* Batch processing checks both `sent_emails` and matching `email_audit` entries for the same post-recipient pair before sending.
* If an audit record proves the email was already sent, the worker marks the queue row as `sent` and backfills `sent_emails` opportunistically instead of resending.
* If provider delivery succeeds but local `sent_emails` persistence fails, the queue row still transitions to `sent` with a warning in `last_error` so the system does not retry the message.

### Claiming Strategy

* The queue worker should only claim rows that it can start in the current processing slice.
* Avoid front-claiming the whole `sendLimit`; stranded `processing` rows interact badly with stale cleanup and can create duplicate delivery attempts after requeue.

## Attendance Exports

The workspace attendance page now supports exporting attendance for everyone across a selected date range.

### Behavior

* The export is inclusive: records on both the start date and end date are included.
* Exports are workspace-wide and include all recorded attendance rows across groups in the selected period.
* The UI builds the file client-side, but the data source remains the authenticated internal API route at `/api/v1/workspaces/:wsId/users/attendance/export`.

### Implementation Notes

* Keep range filtering on the persisted `user_group_attendance.date` string values with `gte(startDate)` and `lte(endDate)` to avoid timezone drift.
* Attendance exports must bind both `workspace_user_groups.ws_id` and joined `workspace_users.ws_id` to the normalized route workspace before returning user names or private email fields.
* Page the API response for large exports instead of assuming a single Supabase page will return every matching row.
* Excel and CSV generation should stay in the client so the page can reuse localized column labels and progress feedback.

## Workspace Member Profiles

Workspace members can have workspace-specific display names, similar to server profiles.

### Behavior

* Member profile display names are stored on `workspace_users.display_name`, not on the global `users.display_name`.
* The workspace members settings page can edit display names for joined members and pending invites.
* Pending email invites may create a `workspace_users` row before the invited platform user joins.
* When the invited user later joins, `workspace_user_linked_users` should reuse exactly one unlinked `workspace_users` row with a matching email in the same workspace.
* If multiple matching workspace profiles exist for an email, runtime linking must not guess. Admin edit APIs should report the ambiguity so duplicate profiles can be resolved explicitly.
* The users database page includes a platform-link repair action for administrators with user-update and private-info permissions. It links only matching platform users who are already workspace members; it never creates workspace membership or merges duplicate workspace profiles.

### Implementation Notes

* Keep members-page writes narrow: update the workspace profile display name only.
* Always verify `manage_workspace_members` before writing member profiles from the members settings UI.
* Enhanced member listing should display the workspace profile name when available and fall back to the platform user display name.
* Auto-linking runs on `workspace_members` insert. If a virtual profile is created or its email is corrected after membership already exists, use the database-page repair action to reconcile the link.
* Repair is conservative: a link is inserted only when one unlinked workspace profile and one existing member platform account share the same normalized email. Missing emails, no member match, duplicate workspace profiles, duplicate platform matches, and platform accounts already linked elsewhere are reported as skipped cases.

## Attendance Manager Counting

Workspace attendance settings include a default-on `ATTENDANCE_COUNT_MANAGERS` flag.

* When enabled, group managers count toward attendance totals in user-group list summaries.
* When disabled, attendance totals on the user-group list subtract group managers from the group member count.
* This is separate from `ATTENDANCE_SHOW_MANAGERS`, which controls whether managers appear in detailed attendance-taking views.

## User Group Activity Audit Logs

User group activity is exposed from the dashboard at `/users/groups?tab=audit-log` and on each group detail page. The feed is normalized from `audit.record_version` through private service-role RPCs so UI code receives one event shape with action, resource type, group, affected user, actor, timestamp, and before/after snapshots.

### Tracked Resources

* Group metadata and status: `workspace_user_groups`.
* Membership and manager role changes: `workspace_user_groups_users`.
* Group tags and default included groups: `workspace_user_group_tag_groups`, `workspace_default_included_user_groups`.
* Posts, post logs, and post checks: `user_group_posts`, `user_group_post_logs`, `user_group_post_checks`.
* Attendance, linked products, metrics, categories, category links, and student metric values: `user_group_attendance`, `user_group_linked_products`, `user_group_metrics`, `user_group_metric_categories`, `user_group_metric_category_links`, `user_indicators`.
* Reports and feedback: `external_user_monthly_reports`, `external_user_monthly_report_logs`, `user_feedbacks`.
* Course content: `workspace_course_modules`, `workspace_course_module_groups`.

### Actor Attribution

* Core group, member, metric, student metric value, and attendance mutations should use the private actor-aware RPCs so service-role writes set `audit.override_auth_uid` before changing rows.
* Other tracked tables still appear when raw audit rows exist. If a route writes with a service-role client and the table does not preserve actor intent in columns such as `creator_id` or `updated_by`, add a focused actor-aware private RPC or route-level activity insert before expanding the UI around that resource.
* Bulk reorder operations are normalized as `reordered` events when the persisted `sort_key` changes.

### Workspace Validation

* Before any admin write, normalize the route `wsId` and verify the target group belongs to that workspace.
* Member writes must also verify every affected `workspace_users.id` belongs to the same normalized workspace before inserting, updating, or deleting membership rows.
* Attendance writes must additionally verify every affected user is already a member of the target `workspace_user_groups_users` row before inserting or deleting `user_group_attendance`.
* Child resource writes should validate their parent group or parent module group before the admin mutation. Do not rely on a raw `group_id` from the URL without checking ownership.

## Topic Announcements

Topic Announcements live under Users and are gated by the workspace secret `ENABLE_TOPIC_ANNOUNCEMENTS=true`.

### Behavior

* Contacts are workspace-scoped and may be raw email contacts or linked to an existing workspace virtual user profile. The linker UI searches workspace users with avatar and email affordances.
* Announcements may optionally reference a real `workspace_user_groups` row through `group_id` for scheduling and reporting context.
* A contact cannot receive announcements until its email is verified through the Tuturuuu internal SES verification flow or through a linked Tuturuuu platform account with a confirmed matching email **and membership in the same workspace**. Platform-wide confirmed accounts must not auto-confirm a contact for another workspace.
* Announcements can include image and PDF attachments. The composer and API enforce the shared email limits: up to 5 files and 10 MB total per announcement.
* Verification emails are sent by the root Tuturuuu sender with normal protected delivery checks, not the rate-limit-bypassing system path.
* Verification links resolve to the public platform origin by default (`https://tuturuuu.com`) so listener hosts such as `0.0.0.0:7803` never leak into recipient inboxes. Set `TOPIC_ANNOUNCEMENT_VERIFICATION_ORIGIN` only when an explicit alternate public origin is required.
* Verification sends enforce a short pending-link cooldown before creating another token, and email delivery applies workspace, user, IP, and recipient limits. The recipient limit is global across workspaces, so one workspace or many workspaces cannot repeatedly send verification links to the same inbox.
* Actual announcements are sent by the enabled workspace sender and are written to `email_audit`.
* The composer and table send flow preview the same server-rendered HTML and text payload that delivery uses. Operators should review that rendered preview before sending or scheduling announcements.
* The first import preset supports foreign-teacher schedule rows with day/date, class, room, time, teacher/contact, email, place, and topic fields. The import UI now uses an editable Excel-like table as the review surface; uploaded spreadsheets, CSV, and direct paste all load into that table so operators can correct every email field before creating drafts or sending valid rows in bulk.
* Workspace-shared **announcement templates** store reusable title, topic starter text, class metadata, optional default group, and default recipient ids. Operators apply a template in the composer, then edit the topic field before sending.
* Operators can fork an existing sent or draft announcement into a new draft. The fork copies title, topic, class context, recipients, and attachment references, but never copies sent, queued, scheduled, or email audit state.
* **Scheduled send** sets `status = queued` and `scheduled_send_at` using the workspace's fixed IANA timezone from calendar settings. Scheduling is blocked while the workspace timezone is still `auto` or unset; use the same calendar timezone gate as the main calendar product.
* The `process-topic-announcement-queue` cron job (every 15 minutes) loads due queued rows and reuses the same `sendTopicAnnouncement` path as manual send. Failed sends keep the existing `failed` / `last_error` handling without infinite retries.
* Announcement list APIs enrich recipient rows with the same `verificationStatus` contract as the contacts list so the table does not treat missing status as unverified.

### Implementation Notes

* Client UI must call `@tuturuuu/internal-api` Topic Announcements helpers; do not add direct client-side fetches.
* Email preview must go through the Topic Announcements preview API and shared renderer instead of a client-only template so previews cannot drift from actual delivery.
* Bulk create/send must keep using the import API plus `send-bulk` helper from `@tuturuuu/internal-api`; do not bypass the shared renderer, recipient verification, or delivery gates for spreadsheet rows.
* Delivery APIs must check the workspace secret, `manage_users`, `send_user_group_post_emails`, recipient verification, and workspace ownership before sending.
* If a contact email changes, previous verification records for the old email must no longer satisfy delivery checks.
* Linked-account auto-confirm checks must join through `workspace_members` for the contact workspace. Do not treat a confirmed Tuturuuu account as sufficient unless that platform user belongs to the same workspace as the linked virtual user.
* Attachment uploads must prepare signed upload URLs through the central workspace storage route, then upload bytes directly to the active storage provider. Do not stream attachment bytes through Topic Announcement-specific Next.js routes.
* User group and course storage uploads are quota-sensitive and must use the server-mediated `uploadWorkspaceUserGroupStorageFile` / group storage multipart route. Do not mint signed upload URLs from this route because the app server cannot verify the final object size after a direct provider upload.
* Attachment rows keep workspace, announcement, storage path, content type, and size metadata. Email delivery downloads the stored files and switches to raw MIME only when attachments exist; regular no-attachment messages should keep the standard SES `SendEmailCommand` path.
