# Mail intake bot - ops handover Version: none (the bot has no version number, see section 10) · Written 2026-10-23 · Escalation: raise a ticket in the **RPA Support** queue ## 1. What the bot does Every 10 minutes the bot checks the referrals mailbox for unread emails. It saves each attachment, checks that it is a PDF under 20 MB and not password-protected, copies it to the shared intake folder, and records one row per attachment in its queue. Ops follow the queue in the **queue viewer** workbook (`queue_viewer.xlsx`, Data > Refresh), which shows id, sender, subject, status and note for each row. The bot drives no desktop app or website. It has no window of its own and runs in the background on a Windows schedule. ## 2. How it works ``` Windows Task Scheduler (every 10 minutes) └─ starts → Mail intake bot (no window) ├─ reads ──> Referrals mailbox (unread emails, marks them read) ├─ copies ─> Shared intake folder (the PDFs) ├─ writes ─> Queue (what you see in the queue viewer) └─ writes ─> Bot log file (dev only) ``` - **Task Scheduler** starts the bot every 10 minutes. Each start is one run. - **The bot** works through the mail and the queue, then stops until the next run. - **Referrals mailbox:** the bot logs in and reads every unread email. When it reads an email, the mailbox marks it as read. - **Shared intake folder:** this is the output folder. Every accepted PDF lands here under its original file name. - **Queue:** one row per attachment, with a status and a note. You see it through the queue viewer. - **Bot log:** one line per finished item, plus warnings. Ops cannot open it, so ask dev to check it. **One run, step by step:** 1. The bot checks whether any queue row is still **Processing**. If one is, it stops the run straight away and nothing else happens (see E04). 2. It logs into the mailbox. If the login fails, it tries 3 times, 30 seconds apart, and then gives up the run. 3. It reads every unread email and saves every attachment. Each attachment becomes a **New** row. 4. It takes the New rows one by one, oldest first, and sets each one to **Processing**. 5. It checks the file. If it is not a PDF or is over 20 MB, the row becomes **Quarantined**. If it is password-protected, the row becomes **Done** with the note "encrypted - not readable" and the file is **not** copied. 6. It copies the PDF to the shared intake folder. The row becomes **Done** (" pages saved"), or **Failed** if the folder could not be reached. ## 3. Before a run - [ ] The bot is set up on the bot machine with its settings file, which holds the mailbox login and the shared folder location. Dev maintains this file and ops never edit it. A missing setting makes every run fail silently (E05). - [ ] The bot's working folders and its queue already exist on the bot machine. On a new install, ask dev to confirm this. Without them, every run fails silently (E05). - [ ] The mailbox password has not expired. When the mailbox password is changed, dev must update the settings file the **same day** (incident 3). - [ ] The shared intake folder opens from the bot machine. - [ ] Rules for senders. The bot can't fix these itself: - Send each referral as a **PDF** under **20 MB**. Bad: `referral.docx`, or a 35 MB scan. Good: `referral.pdf`, 4 MB. - **No password** on the PDF. A protected PDF is never delivered (E02). - Give each attachment a **unique file name**. Bad: every referral called `scan.pdf`. Good: `scan_2026-10-23_0915.pdf`. Two files with the same name overwrite each other (E07). - [ ] Signature logos and inline images in emails show up as Quarantined rows. This is normal and needs no action (E01). ## 4. During a run - Nothing on the bot machine needs to stay hands-off. The bot drives no window, so using the machine doesn't disturb it. - Don't rename, move or delete files in the shared intake folder while a row is Processing. - **To stop the bot:** it is stopped by disabling its scheduled task in Windows Task Scheduler. Ops have no access to that, so raise a ticket. Don't end the bot's process by force: if it is killed mid-item, the row stays Processing and intake stops (E04). - **A run looks stuck when** a row has been **Processing** for more than **10 minutes**, or no new rows have appeared for an hour while senders say they have sent referrals. The bot never recovers from a stuck Processing row by itself, so don't wait. Raise a ticket (E04, E05). ## 5. Where to look when something goes wrong Ops can open two places: the **queue viewer** and the **shared intake folder**. Everything else needs dev. | Question | Where to look | |---|---| | Did the run finish? | No place ops can open shows this directly. Refresh the queue viewer. If no row is **Processing** and new referrals keep appearing, runs are finishing. A Processing row older than 10 minutes means intake has stopped (E04). | | Which items failed? | Queue viewer: filter status = **Failed** or **Quarantined**, and also status = **Done** with a note containing "encrypted". | | Why did one item fail? | Queue viewer, **note** column of that row. For anything else, ticket dev with the row id. | | Did the bot already retry? | For a single item: **no, never**. Failed and Quarantined rows stay that way. Only the mailbox login is retried, and you can't see that. | | Did the PDF actually arrive? | Shared intake folder. Look for the file name shown in the queue viewer. | | Which version is running? | The bot has no version number. Ask dev. | ## 6. Statuses The bot sets no colours. Ops see these as plain text in the queue viewer's status and note columns. | Status - note | Colour | Meaning | What ops does | |---|---|---|---| | New - (empty) | none | Attachment saved from the email, waiting for the next item step | Nothing. It is handled in this run or the next. | | Processing - (note left from before) | none | The bot is working on this row right now. It normally lasts only seconds. | If it is still Processing after **10 minutes**, intake has stopped. See E04. | | Done - " pages saved" | none | PDF copied to the shared intake folder | Nothing | | Done - "encrypted - not readable" | none | **Needs follow-up.** The PDF is password-protected and was **not** copied. It is labelled Done anyway. | See E02 | | Quarantined - "not a PDF or over 20 MB" | none | Rejected. Either it is not a PDF, or it is over 20 MB. The note doesn't say which. | See E01 | | Failed - "share unreachable: " | none | The PDF could not be copied to the shared intake folder. **The bot never retries it.** | See E03 | ## 7. Exception catalogue | ID | What you see | How the bot identifies it | Where it is recorded | Type | Who acts | What to do | Escalate when | |---|---|---|---|---|---|---|---| | E01 | Row **Quarantined** - "not a PDF or over 20 MB" | The file name does not end in .pdf, or the file is bigger than 20 MB | Queue viewer, status and note | Business | Ops | Look at the file name in the viewer. If it is a logo or image from an email signature (.png, .jpg, .gif), ignore it. If it is a real referral, ask the sender to resend it as a PDF under 20 MB. | A file named .pdf that the sender confirms is under 20 MB is still Quarantined. Ticket with the row id. | | E02 | Row **Done** - "encrypted - not readable", and the file is **not** in the shared intake folder | The PDF reports that it is password-protected | Queue viewer, note | Business | Ops | Treat it as **not delivered**. Ask the sender for a copy without a password. The new copy arrives as a new row. | Never, unless an unprotected resend still shows this note. | | E03 | Row **Failed** - "share unreachable: " | Copying the PDF to the shared intake folder raised an error | Queue viewer, note | System | Ops, then Dev | 1. Check that the shared intake folder opens. 2. **Check whether the file is already in the folder.** A partial copy can be left there, so don't just assume it is missing. 3. Raise a ticket so dev can re-queue the row. The bot never retries Failed rows, and ops can't re-queue them. | Always, once the folder works again. Send the row ids, the time, and the note text. | | E04 | One row stuck at **Processing**. No new rows appear at all. | At the start of every run the bot checks for a Processing row. If it finds one, it stops the run and doesn't even read the mailbox. This happens every 10 minutes until someone clears the row. | Queue viewer shows the Processing row. The cause is only in the bot log (dev). | System | Dev | Raise a ticket at once. The bot will **never** clear this itself, and all intake is stopped until dev does. Dev should mark the row Failed, not New, if the file itself caused the crash; otherwise it blocks again. | A row is Processing for more than 10 minutes. Send the row id, its file name, and the time you first saw it. | | E05 | No new rows for a long time, **no** Processing row, and senders say they have sent referrals | Usually the mailbox login fails (expired password, network down) or a setting is missing. The bot tries the login 3 times, 30 s apart, then gives up the run. Each run 10 minutes later does the same. | Nothing ops can see. Warnings are in the bot log, and the final failure appears only in Task Scheduler's run history (dev). | System | Dev | Raise a ticket. If you know the mailbox password was changed recently, say so. | No new rows for 1 hour in working hours while mail is known to be arriving. Send the time of the last new row and of a referral that was sent but never appeared. | | E06 | A sender says they emailed a referral, but no row exists for it | Emails with no attachment are marked read and skipped without any record. If a run crashes while reading the mailbox, the emails it already read are marked read but never queued, and are never picked up again. | Nowhere | Business / System | Ops, then Dev | 1. Ask the sender whether the file was attached, or only linked or pasted. 2. If it was attached, ask them to resend, and raise a ticket. | The email had an attachment. Send the time it was sent. Never send the email itself. | | E07 | Two or more rows show the **same file name**. The file in the shared intake folder belongs to only one of them. | The bot saves each attachment under its original name. A later file with the same name overwrites the earlier one, both in its working folder and in the shared intake folder. | Queue viewer, file name repeated | Business | Ops, then Dev | Open the file in the shared intake folder and check which sender it belongs to. Ask the other senders to resend with a unique file name. | Always. Send the row ids. | ## 8. What the bot recovers by itself | Problem | What the bot does | Limit | After the limit | |---|---|---|---| | Mailbox login fails | Tries again 30 s later | 3 tries (about 90 s) per run | Gives up that run. It tries again 10 minutes later, forever, with no alert (E05). | | New rows left over from a run that stopped early | Picks them up in the next run | none | n/a | | Anything else (Failed row, stuck Processing row, crash) | **Nothing** | n/a | Stays as it is until dev acts (E03, E04, E06) | ## 9. Known incidents None of these three is fixed in code. Each "fix" recorded in the project history is a note only, and the bot behaves exactly as it did before. The project has no version numbers, so the incidents are listed newest first without one. | When / version | What ops saw | Cause | What to do now | Ops could handle? | |---|---|---|---|---| | Before handover | No new referrals arrived, and nobody saw an error. The mailbox password had expired. | The login failure was retried and then the run gave up. The final failure was not written anywhere ops could see. | Not fixed. Use E05: raise a ticket when no new rows appear for an hour. When the mailbox password changes, dev updates the settings file the same day. | Partly | | Before handover | Referrals arrived empty or unreadable in the shared intake folder | Password-protected PDFs were copied without a check | The bot now skips them, but marks them **Done** with "encrypted - not readable". Use E02. | Yes | | Before handover | No referrals arrived for a whole day, and one row sat at Processing | A crash left one row at Processing, which blocks every later run. Someone reset it by hand. | Not fixed. Use E04: raise a ticket after 10 minutes. | Partly (ops can spot it, but only dev can clear it) | ## 10. Releases and version check - **How an update arrives:** dev replaces the bot's files on the bot machine by hand. There is no updater. - **How to confirm it applied:** the bot has no version number, so ops can't check this. Ask dev to confirm in the ticket that the change is live. - **If the version did not change:** n/a. Raise a ticket if behaviour described as fixed still happens. ## 11. Escalating to dev Raise a ticket in the **RPA Support** queue. Send: - the exception ID from section 7 - the queue row id(s) and file name(s) - the time you first saw the problem - the time of the last new row in the queue viewer Never send: passwords, the settings file, the referral PDFs, patient details, or sender email addresses in the ticket or in chat. ## 12. Failure-class check - F1 (missing prerequisite) is covered in section 3 and E05, and in Fix items 5 and 13. - F2 not applicable: the bot drives no window. It talks to the mailbox and copies files directly. - F3 (target slower or different) is covered in E03 and E05, and in Fix item 19 (no time limit on the copy to the shared folder). - F4 (input read differently) is covered in the section 3 sender rules, E01 and E07, and in Fix items 8 and 10. - F5 (done when nothing was done): the normal Done is set only after the copy succeeds. The encrypted Done is covered under F7. - F6 (done but not confirmed) is covered in E03, step 2: check the folder before re-queueing, because a partial copy can exist. - F7 (business exception shown as success) is covered in section 6, E02 and Fix item 3. - F8 (endless re-runs) is covered in section 8, E04 and E05: the bot repeats the failed login and the Processing stop every 10 minutes forever. See Fix items 2 and 4. - F9 (stopping the wrong way) is covered in section 4: stop it only through the scheduled task, via a ticket, and never kill it mid-item. - F10 (one stuck item blocks the queue) is covered in E04 and Fix item 2. - F11 (cause hidden from ops) is covered in section 5, and in Fix items 5, 9 and 12. - F12 (update didn't arrive) is covered in section 10 and Fix item 11. ## 13. Fix before handover (dev team) | # | Gap | Why it hurts ops | Where (file:line) | |---|---|---|---| | 1 | The three "fix" commits (30ad14c, c0af495, fab0a64) only append a comment, so none of the incidents is fixed in code | Ops and dev believe the incidents are fixed | `intake.py:89-91` | | 2 | A row left in Processing makes every later run abort before it reads the mailbox. Nothing catches errors inside `process()`, and nothing resets a stale row. | One bad PDF or one killed run stops all intake until dev edits the DB. Ops can't clear it. | `intake.py:57-68`, `intake.py:74-81` | | 3 | An encrypted PDF is stored as `Done` although it is not copied | Looks like success. The referral is silently missing from the share. | `intake.py:62-63` | | 4 | `RuntimeError("Mailbox unreachable")` is never caught or logged. A login failure is retried like a network failure, 90 s per run, forever. | An expired password goes unnoticed. Nothing alerts anyone. | `intake.py:34-37`, `intake.py:77` | | 5 | Unhandled crashes (a missing `.env` key, missing `data/` or table, a corrupt PDF, a fetch error) write no log line. The traceback goes to the console or Task Scheduler only. | Nobody can tell why intake stopped | `intake.py:31-32`, `intake.py:41-54`, `intake.py:61`, `intake.py:71-84` | | 6 | RFC822 fetch marks mail read before the rows are committed | A crash mid-fetch loses referrals for good (E06) | `intake.py:43-54` | | 7 | `Failed` rows are never retried. Only `New` is selected. | A short share outage needs a dev DB edit for every row | `intake.py:66-67`, `intake.py:78` | | 8 | Attachments are saved under their original name in `data/tmp` and the share, with no unique prefix | Same-name referrals overwrite each other (E07) | `intake.py:50`, `intake.py:65` | | 9 | One note covers both "not a PDF" and "over 20 MB" | Ops can't tell the sender what to fix | `intake.py:59-60` | | 10 | Every named MIME part is queued, including inline images | Quarantined noise in the viewer | `intake.py:47-49` | | 11 | There is no version string anywhere | Ops can't confirm an update was applied | `intake.py` (none) | | 12 | Ops have no access to the log and there are no alerts. Run-level failures (E04, E05) are invisible in the queue viewer. | Ops only notice when senders complain | `intake.py:23-24`, `intake.py:75` | | 13 | Nothing creates `data/`, `data/tmp/`, `logs/` or the `queue` table | A fresh install crashes on every run | `intake.py:17-18`, `intake.py:23`, `intake.py:73` | | 14 | Nothing guards against two runs overlapping | The same `New` row can be processed twice if a run takes over 10 minutes | `intake.py:71-84` | | 15 | `main()` writes a `BOT_WAS_RUN` file on every run, and its purpose is unknown | Unexplained file in the bot folder | `intake.py:72` | | 16 | The sample log contains a line in an old format that includes a sender email address. The log never rotates. | Possible PHI/PII in a file that may be shared | `logs/intake.log:6`, `intake.py:23` | | 17 | The share path falls back to a hardcoded server path when `INTAKE_SHARE` is missing | A missing setting silently copies to the wrong place | `intake.py:19` | | 18 | `queue_viewer.xlsx` is not in the repo, so its columns and colours can't be verified | The handover relies on an unversioned file | `README.md:6` | | 19 | The copy to the share has no timeout | A hung share leaves the row in Processing, which then becomes E04 | `intake.py:65` |