# PRD — ieb-inde-6 (IEB Postbox)

Evidence date: 2026-10-12. Inspected revision: `main` at `d9e4836`.

Provenance labels: **Documented** (readme or SQL seed text), **Implemented** (code path), **Inferred** (reasonable reading of that code), **Unknown**.

The website root used below is `INDE-6/Evolution Software Unified Framework/Evolution Software Unified Framework/core_custom/INDE-6/`.

## Product purpose

Postbox is the INDE-6 website in the Evolution Software family. `INDE-6/ReadME.txt` states **Postbox = INDE-6**. The implemented behaviour lets operators keep school file areas, copy a master folder layout onto a new canvas, replicate a group upload to linked schools, notify school contacts or the IEB submissions mailbox, track downloads, and create a regional moderator login with subject canvases.

The same website still contains the framework's admin, catalogue, loyalty, and medical surfaces. Those are present in the tree. This PRD does not treat them as Postbox requirements unless a Postbox class calls them.

## Users and roles

`business/enums/pNums.cs` defines:

| Value | Name |
|---|---|
| 1 | WebsiteUser |
| 2 | PowerUser |
| 3 | SuperUser |
| 4 | AdminUser |
| 5 | CustomUser |

**Implemented** behaviour that names these roles:

- A new regional moderator is stored as `CustomUser` with `mimicUserType` = `WebsiteUser`, terms accepted, reset required, and active. `PostboxHelper.CreateUserForModerator`.
- Group canvases are saved with `userTypes` `1,2,3,4` (Website through Admin, not Custom). `GroupPostbox_form.ascx.cs`.
- Regional-moderation canvases are saved with `userTypes` `1,2,3,4,5`, `isRestricted` true, and `customUsers` set to the new moderator id. `PostboxHelper.CreateSpecificCanvas`.
- Regional-moderation document mail treats the uploader as a moderator when `userType` is WebsiteUser or PowerUser. It does not read `mimicUserType`. `PostboxEmails.SendRegModDocsUploaded`.

School contacts are published surface rows (`oSchoolContacts`), not a separate login type in the code that was read. They are the recipients of school notification mail.

How the framework combines `userTypes`, `isRestricted`, and `customUsers` at page-render time was not fully traced. **Unknown / Requires Confirmation.**

## Major workflows

### 1. Save a group postbox

Trigger: save of the Group Postbox surface. `GroupPostbox_form.ascx.cs` `SaveSurfaceFormCustom`.

For group type Primary (`1094`) or Secondary (`1095`), the code takes the first letter (`P` or `S`) and, if missing, creates three canvases under parent ids Materials `5561`, Correspondence `3148`, and Records `2109`:

- Name pattern `Group{Materials|Correspondence|Records}{GroupNameWithoutSpaces}{P|S}`.
- Display pattern `Group{type}: {GroupName} [{P|S}]`.
- Each canvas gets canvas metadata with module id `65` (`Module.FileExplorer`).
- Folders are copied from `~/upload/explorer/_{Primary|Secondary}Master_{Materials|Correspondence|Records}` onto `~/upload/explorer/{canvasName}`.

The published `publ_GroupPostbox` row stores group name, group type, description, and the later-added assessment, correspondence, records, and linked-schools fields.

### 2. Upload a file in the explorer

Trigger: Telerik `StoreFile` in `controls/module/fileExplorer.ascx.cs`.

The code writes `oFileExplorerMetaData` (name, path, date, extension, size text, explorer type File, uploaded-by display name). On a new file it branches:

| Path contains | Action |
|---|---|
| `groupMaterials`, `groupCorrespondence`, or `groupRecords` | `ReplicateFileInGroup`, which copies the file onto each linked school's matching canvas and emails that destination |
| `submissions` (case-sensitive `Contains` on the enum name lowercased, so `submissions`) | Copy into the School Submissions canvas (`2129`), under `Primary` or `Secondary` when the source canvas name ends in `p`, store origin `{School} [{type initial}]`, then `SendPostboxEmail` on the source |
| `regmoddocs` | `SendRegModDocsUploaded` only (no copy) |
| `regmodreport` | Copy into canvas name `RegionalModeration` under `{Primary\|Secondary}/{subject}`, then `SendRegModReportUploaded` |
| anything else | `SendPostboxEmail` |

Replacing an existing metadata row updates the row and does not send mail. **Implemented.**

`SendPostboxEmail` only sends when the canvas display contains `Materials`, `Correspondence`, or `Records` (school contacts) or `Submissions` (IEB submissions address). Circulars and newsletters fall through this method and send nothing. **Implemented.**

### 3. Download a file

Trigger: download button in the file explorer.

One file: read bytes, call `PostboxDownloadTracking.saveToDownloadTracking`, return the file.

Several paths: build `Combined.zip` with Ionic.Zip, then `saveDirectoryToDownloadTracking` for each expanded file.

The tracking row is a published `FileExplorerDownloads` item: date, user display name, email, file name, parent folder, subfolder, school name, and file-metadata id.

### 4. Create a regional moderator

Trigger: surface save when setup code is `INDE-6` and the surface name is `RegionalModerators`. `controls/surface/surface_form.ascx.cs`.

On a new item (`recId == 0`), `CreateUserForModerator` runs with the form's first name, surname, email, and contact number. If that email already exists for the setup customer code, the method returns true and does not change the user. If the user is created, a welcome email is sent and the user id is appended to the `SchoolPostbox` canvas `customUsers` list.

Then `RegionalModerationSubjectUpdated` runs for up to three pairs: subject fields ending `Subject1` / `Subject2` / `Subject3`, and multi-picklists ending `Subject1Schools` / `Subject2Schools` / `Subject3Schools`.

For each school id in the list, when the subject is not already on that school postbox, the code inserts a `RegionalModerationSubjects` row (level, school postbox name, subject code, subject name, unique name, moderator email, parent item id). If the canvases do not exist, it creates:

- `Reg Mod Docs:{school postbox} - {unique subject}` under canvas name `RegionalModerationDocs`
- `Reg Mod Report:{school postbox} - {unique subject}` under canvas name `RegionalModerationReport`

Canvas names strip spaces and these characters: `: . ( ) ' , [ ] - / \`. Each new canvas gets a file-explorer module and a file-explorer config: view, add, and delete roles `1`, max file size `104857600` (100 MB), paging on, page size 20. Folder trees are copied from `~/upload/explorer/_{Primary|Secondary}Master_RegModDocs` and `_RegModReport`.

School type for that copy: the first `[X]` in the display is Primary when it contains `P`, otherwise Secondary. **Implemented.**

### 5. Browse the explorer grid

The grid adds Creation Date and Owner Name. School Submissions (`2129`) and the canvas named `RegionalModeration` also show School Name from metadata `origin`.

Default sort when the user has not chosen a column: Circulars (`2113`) sort by name ascending; other file grids sort by date descending. Folders sort before files in the date comparer.

## Integrations

| System | Direction | Trigger | Evidence |
|---|---|---|---|
| SQL Server `Framework_INDE-6_Demo` | App reads and writes | Every surface and file action | Active `conn` app setting. Credentials omitted. |
| SMTP `smtp.evolutionsoftware.co.za` port 25 | Outbound mail | Postbox emails and the framework mailer | `Web.config` `mailSettings`. Password omitted. |
| WCF `http://localhost:58913/framework_service.svc` | Client endpoint named `Iframework_service` | Present in config | Security mode `None`. |
| WCF `http://service.ieb.co.za/framework_service.svc` | Client endpoint named `Iframework_serviceLive` | Present in config | Same binding. |
| `http://sms.netgen.co.za/SMS_V2.asmx` | Outbound SMS | Framework SMS client | Endpoint present. SMS user and password are in app settings. Values omitted. |
| MediSwitch QA gateway hosts | Configured | `MediSwitchResponses` = `0` | Live gateway URLs are commented out. |
| Country web services at webservicex.net | Configured | `getCountriesFromWebService` = `0` | Not called while the switch is 0. **Inferred** from the flag. |

Failure behaviour for mail: `SendPostboxEmail` and `SendWelcomeEmail` throw on failure; `SendRegModDocsUploaded` and `SendRegModReportUploaded` catch and call `exception.HandleException`. Group replication catches per school and continues. **Implemented.**

## Functional requirements

### FR-001 — Identify the site as INDE-6 Postbox

The readme names the project Postbox = INDE-6. Email subject seeds say "IEB Postbox". Surface-form moderator logic runs only when `handler.ReturnSetup().code` equals `INDE-6`.

Evidence: `INDE-6/ReadME.txt`, SQL `INDE-6-1.0.1` and `1.0.3`, `surface_form.ascx.cs`.

Implementation: Implemented. Confidence: High.

### FR-002 — Create group file canvases from a master folder

Saving a new group name and type creates Materials, Correspondence, and Records canvases when that canvas name is absent, and copies the matching master directory.

Evidence: `GroupPostbox_form.ascx.cs`, `PostboxHelper.CopyDirectory`.

Implementation: Implemented. Confidence: High.

### FR-003 — Fan a group upload out to linked schools

A new file whose path contains `groupMaterials`, `groupCorrespondence`, or `groupRecords` is copied to each linked school's canvas of that kind, metadata is saved, and `SendPostboxEmail` runs for the destination.

Evidence: `fileExplorer.ascx.cs` `ReplicateFileInGroup`.

Implementation: Implemented. Confidence: High. The link between group and school rows is read from published group-school data inside that method. The exact column list is the `oGroupPostboxSchools` object.

### FR-004 — Notify school contacts of materials, records, and correspondence

For those canvas display names, mail goes to every active `publ_` school contact with the same school name and school type, joined with `;`. Empty recipient list skips the send. Subject comes from setup `emailSubjectToSchoolContacts`. Body comes from template name `E-mail to School Contacts Template` (template type Postbox). From address is setup `communicationEmail`. Send is started on a background `Task`.

SQL seed subject: `IEB Postbox - New Document Notice [{ParentFolder}]`.

Evidence: `PostboxEmails.SendPostboxEmail`, SQL `INDE-6-1.0.0`, `INDE-6-1.0.1`.

Implementation: Implemented. Confidence: High.

### FR-005 — Notify the IEB submissions mailbox of a school submission

When the canvas display contains `Submissions`, the recipient is setup `submissionsEmail`. SQL seed sets that column to `submissions@ieb.co.za` and the subject to `IEB Postbox - New Document Submission [{SchoolName - SchoolType}]`. Body template name: `E-mail to IEB Contacts Template`.

Evidence: `PostboxEmails`, SQL `INDE-6-1.0.2` through `1.0.4`.

Implementation: Implemented. Confidence: High. Whether the live setup row still has that address is **Unknown**.

### FR-006 — Copy school submissions into a shared submissions canvas

A path containing `submissions` is copied under the School Submissions parent canvas, into a `Primary` folder when the source canvas name ends with `p`, otherwise `Secondary`. Origin is `{school name} [{first letter of school type}]`. The source upload is then emailed via FR-005.

Evidence: `ReplicateFileInSchoolSubmissions`.

Implementation: Implemented. Confidence: High.

### FR-007 — Track downloads

A single-file download and each file inside a multi-select zip write a `FileExplorerDownloads` published row for the logged-in user.

Evidence: `PostboxDownloadTracking`, SQL `INDE-6-1.0.5` (`publ_FileExplorerDownloads`).

Implementation: Implemented. Confidence: High.

Edge: `saveDirectoryToDownloadTracking` indexes the original selected paths in parallel with the expanded file list. A folder that expands to more files than selected paths can throw. **Inferred** from the loop. Not covered by a test.

### FR-008 — Provision a regional moderator on first save

See workflow 4. Welcome mail uses an outbound template whose name contains `welcome email`, subject `IEB - {template name}`, and a reset code encrypted into `{encCode}`. From address is setup `communicationEmail` and the sending display name is `sendingEmailName`.

Evidence: `PostboxHelper.CreateUserForModerator`, `PostboxEmails.SendWelcomeEmail`.

Implementation: Implemented. Confidence: High.

### FR-009 — Open regional-moderation document and report canvases per subject

See workflow 4. An empty school-postbox id or empty unique subject returns false and creates nothing. An existing subject or canvas is left as it is.

Evidence: `PostboxHelper.RegionalModerationSubjectUpdated`.

Implementation: Implemented. Confidence: High.

### FR-010 — Email regional-moderation uploads

Document upload (`regmoddocs`):

- WebsiteUser or PowerUser: mail the moderator address stored on `RegionalModerationSubjects` for that school postbox and unique subject. BCC is `communicationEmail`. Subject and body use the "to Moderator" templates.
- Any other user type: mail active school contacts for that school name and type. Subject and body use the school templates.

Report upload (`regmodreport`): copy into `RegionalModeration`, then mail `communicationEmail` to itself, using the moderator subject and body templates.

Evidence: `PostboxEmails.SendRegModDocsUploaded`, `SendRegModReportUploaded`, SQL `INDE-6-1.0.18` and `1.0.19`.

Implementation: Implemented. Confidence: High.

**Requires Confirmation:** a moderator created by FR-008 is `CustomUser`, so a later upload by that account takes the school-contact branch, not the moderator branch.

### FR-011 — Show date, owner, and sometimes school on the file grid

See workflow 5. File and folder metadata supply Date, Owner, and Origin.

Evidence: `fileExplorer.ascx.cs` `AddDateAndOwnerColumns`, `CustomColumnsContentProvider`.

Implementation: Implemented. Confidence: High.

### FR-012 — Delete and move keep metadata in step with the file

Delete file and delete directory remove the matching metadata row, then delete the filesystem object. Create directory writes directory metadata. Move and copy paths update metadata in the same provider (methods continue below `MoveFile`).

Evidence: `CustomColumnsContentProvider`.

Implementation: Implemented for delete and create. Confidence: Medium for move and copy, which were not line-traced past the method signatures.

### FR-013 — Gate `/upload` when the handler runs

`FileProtectionHandler` is registered for path `upload`. On GET, if the URL is not the site root and not `{WebAddy}/admin`, a missing session user redirects to `{WebAddy}/admin`. A signed-in user is sent the file. Other HTTP methods do nothing.

Evidence: `App_Code/FileProtectionHandler.cs`, `Web.config` handlers.

Implementation: Implemented for requests that hit this handler. Confidence: Medium. The path attribute is the literal `upload` with no wildcard. Whether `/upload/explorer/...` is included is **Unknown / Requires Confirmation**.

### FR-014 — Keep examination-authoring behaviour compiled in

`examHandler` still saves collaboration logs, emails exam status, resolves subject resource roles (Examiner, Reviewer, Internal Moderator, External Moderator, Assessment Specialist), and renders compliance tables. `surfaceNotes.ascx.cs` and `attachments.ascx.cs` call it. Subject status and compliance enums are in `examNums`.

The readme names Exams as INDE-1, not INDE-6. Provenance: **Implemented** in this tree; whether Postbox operators use it is **Unknown / Requires Confirmation**.

## Constraints

- Culture app setting `defaultCulture` is `en-ZA` (the key is duplicated). `globalization` is `uiCulture="en"` and `culture="en-US"`.
- Session timeout is 60 minutes.
- `httpRuntime maxRequestLength` is `10048` (kilobytes, about 9.8 MB). New regional-moderation explorer config allows 100 MB, and the file explorer also sets `MaxUploadFileSize` to `104857600`. A request larger than `maxRequestLength` will be rejected by ASP.NET before the explorer limit applies. **Requires Confirmation** of the limit operators expect.
- New moderator passwords and welcome reset codes are encrypted with hardcoded passphrases in source. The passphrases are omitted here.
- `Web.config` `debug` is `true`. `WebAddy` is `http://localhost:60629`.
- App setting `website` is set twice: `IEB (INDE-6 Local)` and later `IEB CRM`. `ConfigurationManager.AppSettings` returns every value for a duplicated key. Which label the UI shows is **Unknown / Requires Confirmation**.
- Parent canvas ids and school-type lookup ids are literals. They only work if the target database uses the same ids.
- School-type parsing is not one rule. `DetermineSchoolNameAndType` maps letter `S` to Secondary and every other letter to Primary. `CreateCanvas` maps a bracket token containing `P` to Primary and every other token to Secondary. A token other than `P` or `S` is classified differently by the two methods.
- Address fields with an empty fifth part are stored as South Africa. `PostboxHelper.SetAddressFieldValue`. That helper is on the regional-moderation field builder. **Implemented.**
- Group canvas creation casts the group-type picklist to `PostboxCanvasNums.SchoolType`. That enum's values are `Primary = 1094` and `Secondary = 1095`. A different picklist id will not produce the folder names `Primary` and `Secondary`.

## Planned behaviour

No roadmap, TODO issue list, or feature flag for unfinished Postbox work was found. Commented blocks (patient registration for setup code `SHOU-1`, live MediSwitch URLs, loyalty email switch `0`) are dormant framework code, not a stated Postbox plan.

## Unknown / requires confirmation

- The full name of IEB, and whether this site is still the production Postbox.
- Which database catalog and which website project (`core`, `core-dev`, `core_custom/INDE-6`) operators deploy.
- Whether canvas ids `5561`, `2109`, `3148`, `2129`, `6097`, `6098`, and `2113` exist in the current database.
- Whether cluster surfaces are part of Postbox or an unused generated module.
- Whether examination authoring (INDE-1-shaped objects) is in use on this site.
- Live values of `communicationEmail`, `submissionsEmail`, and the email templates. The SQL seeds are evidence of what was inserted in 2019–2020, not proof of the current row.
- Upload size limit that production actually enforces.
- Whether the file-protection handler covers nested upload URLs.
- Contents of `release/1.0.0.1` and `release/1.0.5`.
