-
-
Notifications
You must be signed in to change notification settings - Fork 29
CalDAV and CardDAV
Status: ALL SEVEN STEPS ARE BUILT. CardDAV contact sync works in both directions (the import shipped in 1.8.0, the write-back in 1.9.0), and CalDAV calendar sync is built too: scheduled tickets and tasks go into a calendar on any CalDAV server, two-way, signed in as each analyst. See Scheduled work in your own calendar Β§3b for CalDAV, CardDAV contact sync for contacts, and the developer guides linked from each. This page is kept as the analysis that led there.
Raised by mbsouth in #133, who runs SabreDAV for both calendars and contacts.
β The estimate in Β§5 held up. It called this "a new fetch-and-map layer under an existing policy layer β genuinely smaller than starting from nothing, and genuinely larger than adding a flavour", and that is exactly what it turned out to be: about 1,000 lines across two new files, with nine policy-layer functions reused unchanged and one touched additively. Β§5 also predicted the test fixture would be needed "or it ships untested against a real server" β
docker/carddav-test/exists because of that sentence.
The work was planned as seven steps. Five shipped in 1.8.0, the write-back followed in 1.9.0, and CalDAV came last.
| Step | State | |
|---|---|---|
| 1 | Show the person fields on the Edit user dialog (no CardDAV involved) | β Done β all seven, on both people screens. Contact details |
| 2 | Let a portal user edit their own (no CardDAV involved) | β Done β job title, office, phone, mobile |
| 3 | Connect to a CardDAV server | β
Done β includes/carddav.php, plus a connection test that enumerates the server's address books |
| 4 | Import contacts from one group | β Done, and wider than planned β the server enumerates its own groups and categories, and you tick as many as you like |
| 5 | Write changes back (the hard one) | β Done β surgical vCard editing, a per-field three-way merge, and a permission probe. CardDAV write-back internals |
| 6 | A test server to develop against | β
Done β docker/carddav-test/, BaΓ―kal 0.12.1 |
| 7 | CalDAV | β Done - a second provider under the existing calendar sync, two-way, signed in as each analyst. Calendar sync Β§3b, developer guide Β§3 |
π The import is still provably read-only, and that claim survived step 5. PUT lives in a separate file, includes/carddav_write.php, that the import path never loads β so "the import issues only PROPFIND and REPORT" remains a fact about the code rather than a promise. Write-back is off by default and gated on its own column.
The predictions below were written before it was built. Two held, one was answered differently, and one turned out to be the wrong worry.
- β The conflict rules were the real cost, exactly as Β§10 said. The answer is a per-field three-way merge: re-read the card, compare the server's current value against what FreeITSM held before the save, and refuse only on a same-field difference.
- β
The ETag groundwork paid off. It was already being read and discarded; it is now stored per person and sent as
If-Match. - π "Precedence" turned out to be the wrong frame. There is no per-field timestamp and there did not need to be β the question is not whose value is newer but did anyone else touch this same field since we last looked, which the three-way merge answers without a clock.
- π The scope question was settled by the vCard, not by policy. The writable set is the five details a card can actually hold; employee ID and manager have nowhere to go. Office is in, resolving the open question below.
β οΈ The failure mode worry was right and shaped the design. The answer to "silently overwriting a corrected number is hard to notice and harder to undo" is that FreeITSM never generates a card β it edits the one line that changed and leaves every other byte alone.
Not a CardDAV client β there is one, and it works. What is missing is the part that was always the real cost:
- Precedence. When a phone number differs on both sides, which wins? "Last write" needs a per-field timestamp neither side reliably has.
-
What a person owns about themselves. Β§8 phase 3 named phone, mobile and job title β deliberately not department, employee ID or manager, because those are the service desk's to state rather than the person's.
β οΈ Note that the local answer shipped as four fields, not three:USER_SELF_EDITABLE_FIELDSisjob_title, office, phone, mobile. Whoever builds step 5 should decide whether office joins the write-back list too β the argument that a person knows their own site better than the service desk does is the same argument that put it in the portal. - The failure mode is worse than not having it. Silently overwriting a corrected number in somebody's address book is harder to notice, and harder to undo, than never having pushed it.
-
A
PUTneeds an ETag to avoid clobbering a card somebody edited meanwhile. The transport already reads ETags on fetch and simply does not use them yet, which is the one piece of groundwork that is done.
β This was the half mbsouth actually argued for, and his Article 16 case is now answered as a setting. Write-back first sent only an analyst's edit. His scenario is a customer correcting their own details, and that is now possible from the portal too, off by default because it lets customers write into the operator's address book: System β Portal profile β Let them change these too, and send their change to the address book. The customer's change goes to the card first and is saved in FreeITSM only once the address book accepts it. See Contact details.
He replied on 8 September, and it settles Β§6 as well as correcting something in Β§4.
- The fork is answered. One-way "would be much better than none and a very good start" β but the focus should also be on two-way. His reason is the sharp one: the change surfaces during the phone call, and having to fix it in a second application is exactly what an ITSM tool should remove. Β§10 says an answer like that is what would revive this.
- π΄ He disagrees with "nobody is ever deleted" (Β§4 rule 1), and wants a person removed once their last relationship goes. FreeITSM already agrees with him more than Β§4 implies:
api/tickets/delete_user.phpis FK-safe rather than absolute β a person with no tickets and nousers_assetsrows deletes outright, and the refusal only fires when something still points at them, naming it. What does not exist, and should not, is a sync deciding that by itself: a source that stops returning somebody is not evidence they should be removed. - β A legal-basis argument that was nowhere in this page. If the customer database is the record of who you have a lawful basis to process, then "is this caller in the address book" stops being a lookup and becomes a different question with different consequences for the analyst. That makes the sync more than a copy of some fields, and it is the most interesting idea in the thread.
includes/directory_sync.php writes to users, the requester table, and mentions analysts in exactly three places, all "who triggered this run". So the policy layer already governs precisely the records he cares about. The distance to a CardDAV source is shorter than Β§4 makes it sound β the policy is there and it is the transport that is missing.
They arrived together and they are not the same size at all, so this page treats them separately.
| What it is | Size | Case for it | |
|---|---|---|---|
| CalDAV | A read/write calendar protocol | Moderate, self-contained | Good, but partly already served |
| CardDAV | A contacts protocol β really, a request to stop keeping contacts in two places | Larger, and touches the person record | Stronger |
The second is the interesting one. The first is closer to being answered already than it looks.
FreeITSM already publishes an iCalendar subscription feed (Ticket calendar sync), which is the read-only half of what CalDAV does. Any client that speaks CalDAV also speaks iCalendar subscriptions, so somebody running SabreDAV can already get FreeITSM's scheduled work into their calendar today.
What CalDAV would add on top:
- Writing back. Move an appointment in your calendar and have the ticket's scheduled work move with it. That is the real prize, and it is also the whole difficulty.
- Faster updates. A subscription is polled on the client's own schedule β often hours. CalDAV can be pulled on demand.
The honest counter is that FreeITSM already has a two-way route, the Microsoft Graph push, and a second two-way route is a second set of conflict rules, a second set of "who wins when both sides changed", and a second thing to keep correct forever. The value of CalDAV is not that it does something new; it is that it does the same thing without requiring one particular vendor. That is a real argument β it is just not an argument about capability.
The reporter's fair observation. He noted that "Microsoft solutions seem to be preferred". That is about the order things were built in rather than a preference: the Graph route was built first because it was what was being asked for, and the iCalendar feed exists precisely so that it is not the only option. The principle on record is that "your calendar system is not implemented" must never mean "you get nothing".
Strip out the protocol and the request is this:
The people in FreeITSM are an island. Every customer, partner and colleague already exists in one address book. Setting FreeITSM up meant typing a large number of them in a second time, and from that moment the two lists start drifting apart.
He also asked for something more specific and more sensible than "sync everything": scope it to one group in the address book β an itsm group β so FreeITSM receives the contacts that are relevant to it and not a personal address book of several thousand entries.
That scoping instinct is right, and it matches how FreeITSM already treats directories, which restrict what they import by search scope rather than swallowing the whole tree.
He cites Article 16 (the right to have your details corrected), Recitals 64β66 and Article 19 (telling everyone you passed the details on to).
The argument is practical rather than legal. If a customer can correct their own phone number in the self-service portal, and that correction flows back to the address book, then the customer does the correcting instead of the service desk. That is what "we can leave that up to our customers" means β it converts a manual obligation into a self-service one.
It is a good argument, and it is worth noticing what it actually requires: the write-back direction, which is the harder one. A read-only import of contacts does not help with Article 16 at all.
FreeITSM has already built most of the shape of a contact sync, for LDAP and Active Directory. The person record in users already carries provenance:
| Column | What it records |
|---|---|
auth_provider_id |
Which external source this person came from |
directory_username |
Their identity in that source |
is_managed |
This record is owned by the source, not by hand |
last_seen_in_source |
When the source last confirmed they exist |
sync_missed_count |
How many consecutive runs have not seen them |
And includes/directory_sync.php enforces three rules that were expensive to learn and would be identical for any other source:
-
Nobody is ever deleted. Assets, tickets and handover documents all hang off
users.id. Someone who has left is deactivated and keeps their history. - A run that looks wrong changes nothing. If a source suddenly returns far fewer people than last time, that is far more likely to be a misconfiguration or a slow replica than a redundancy round. A sanity brake stops the run rather than deactivating a company.
-
Missing once is noise. Nobody is deactivated on a single absence β
sync_missed_counthas to reach a threshold, and any sighting resets it.
There is also a preview mode that runs the identical code path and writes nothing, so what an administrator is shown is what would happen rather than a separate estimate that can drift from it.
None of that would need rebuilding. A CardDAV source would reuse the entire policy layer. See Directory sync and Importing people from a directory.
It is tempting to read the above and conclude this is a small job, so this section exists to stop that.
Extending directory sync Β§2 documents how to add another kind of directory, and every one of its extension points assumes LDAP is the transport:
- the configuration columns are named
auth_providers.ldap_attr_*; -
dsyncFetchPeople()pages results usingLDAP_CONTROL_PAGEDRESULTS; - identity handling is
objectGUIDversusentryUUID, both LDAP concepts; -
manageris assumed to hold a distinguished name.
CardDAV is HTTP and vCard. It has no DNs, no paged LDAP controls, and its identity is a URL plus an ETag. So the work is a new fetch-and-map layer under an existing policy layer β genuinely smaller than starting from nothing, and genuinely larger than adding a flavour. Anyone estimating this should read that section first and price the transport honestly.
There is a second, quieter catch: the test fixture. docker/ldap-test/ runs Samba AD and OpenLDAP side by side precisely so a new flavour cannot be added blind. A CardDAV source would need its own equivalent, or it ships untested against a real server.
Everything above collapses into one question, and it is the reporter's to answer:
| What it does | Cost | Serves the GDPR argument? | |
|---|---|---|---|
| One-way (address book β FreeITSM) | Stops contacts being typed twice, and keeps them current | Much smaller. Reuses the whole policy layer | No |
| Two-way (also FreeITSM β address book) | The above, plus customers maintaining their own details | Much larger β conflict rules, precedence, what happens when both sides change | Yes |
One-way probably delivers most of the day-to-day value. Two-way is what he actually argued for.
Before any of this, there is a gap inside FreeITSM itself.
The self-service portal does not let people edit their own contact details. A signed-in requester can change their password, their theme, and their preferred name β and that is all. Not phone, not mobile, not job title, not department, not office.
So the self-service rectification the whole GDPR argument rests on does not exist yet locally, before any question of syncing it anywhere.
That is worth separating out, because it is:
- small, compared with everything else on this page;
- useful to every installation, whether or not CardDAV is ever built;
- a strict prerequisite for the two-way case.
If any part of this page is ever acted on, that is the piece to do first, and it can be done on its own merits.
β It was, in 1.8.0. A portal user can now maintain their own job title, office, phone and mobile. Deliberately not department, employee ID or manager β the reasons differ per field and are set out in Contact details; the short version is that nobody should choose their own approver. So the rectification the GDPR argument rests on now exists locally, and if the two-way half is ever built it has somewhere to write to and a defensible list of what a person owns about themselves.
π The useful discovery was that this prerequisite was even smaller and even more missing than the page thought. The seven columns had existed since directory sync slice 1, api/tickets/save_user.php had accepted and guarded them the whole time, and Assets β Users had edited them from the start β but Tickets β Users, the screen an analyst actually lands on when looking up a requester, had none of them and its endpoint did not return them. A blue-sky page about a protocol turned up a plain inconsistency two screens deep.
- β
Let a portal user maintain their own contact details. BUILT β 1.8.0. Independently useful. Prerequisite for everything below.
β οΈ It turned out to be two steps, not one: the fields were missing from Tickets β Users as well, andapi/tickets/get_users.phpdid not even return the columns β so FreeITSM had two people editors of different depth before it had a portal one at all. Both now show the same seven; the portal offers the four a person owns about themselves. See Contact details. - β
Read-only CardDAV import, reusing the directory-sync policy layer. BUILT β 1.8.0. Delivers "stop typing contacts twice".
β οΈ One thing changed on the way: the plan said "scoped to one group", which is what was asked for. It ships with the server enumerating its own groups and categories and the operator ticking whichever they want βcardDavScanBook()has to read every card to count them anyway, so offering a list cost nothing over accepting a typed name, and it removed the need to explain which of the two conventions their address book uses. See CardDAV contact sync. - β Write-back of the fields a person owns about themselves β phone, mobile, job title. Deliberately not everything: the fields the service desk owns should not be overwritten from outside. BUILT β 1.9.0.
- β CalDAV, as a separate piece, on its own argument. BUILT. It turned out to be a second provider under the calendar sync that already existed for Microsoft, not a new sync: the "which ticket goes in whose calendar" logic, the three pull guards and the event map were all reused unchanged. The genuinely new part was that FreeITSM has to sign in as each analyst, because a CalDAV server has nothing like Microsoft's tenant-wide app permission.
Phase 3 is where the conflict rules live, and it should not be started until 1 and 2 have been in use long enough to know what actually drifts.
- Uncertain demand. One request, from one installation, from somebody already running a DAV server. That is a real need and not yet evidence of a common one. CalDAV and CardDAV are the sort of thing that either matters enormously to a site or not at all.
- Complexity versus reward for the two-way half specifically. A second bidirectional sync route is a permanent maintenance commitment, and the failure modes of a contact sync that gets precedence wrong are worse than not having one: silently overwriting a corrected phone number is harder to notice, and harder to undo, than never importing it.
- The cheap half was not blocked by any of this, and was done first β which is the point rather than a caveat. Phase 1 needed no protocol work at all, and splitting it off got the reporter something real while the protocol question stayed open. β Shipped in 1.8.0, and phase 2 followed the same day once it was clear how little the transport actually needed.
β οΈ The demand argument did not change, and the work happened anyway. It is still one site asking. What moved was the cost: reading the policy layer properly showed it had no LDAP in it, so the one-way import was a fetch-and-map layer rather than a second sync engine. Worth remembering as a pattern β "uncertain demand" and "expensive" were doing the parking jointly, and only one of them turned out to be true.
Being here is not a rejection. The analysis is on record so it does not have to be re-derived. β Nothing from this request is parked any more. Both directions of the contact sync are built, and so is CalDAV.
- β Phase 2 shipping, which is now done β and it changed the write-back question rather than answering it. Correct, and the remaining cost was indeed the conflict rules.
- π Feedback from the one site now running it β called "the thing most likely to move it". It is not what moved it: the write-back was built before that feedback arrived. Which fields actually drift is still unknown, and is still worth asking.
- β A second and third installation asking. Still outstanding: it remains one site, and that never changed.
- β A concrete answer to Β§6 from somebody who would actually use it. Given β see Β§0. He would take one-way as "a very good start" but wanted two-way as the target.
- Interest in CalDAV specifically from anyone who cannot use the Microsoft Graph route, which is the argument that route cannot answer. β Built - and again not because demand grew: the calendar sync had been written with a provider layer from the start, so CalDAV was a provider rather than a project.
- CardDAV contact sync β what shipped, for the person running it
- CardDAV contact sync β developer guide β transport, auth and vCard parsing
- CardDAV import internals β scope, the run lifecycle and the test rig
- Directory sync β the existing import, and the three safety rules
- Extending directory sync β Β§2 in particular, before estimating anything here
- Importing people from a directory β the administrator's view
- Ticket calendar sync β both calendar routes as they stand today
- #133 The calendar subscription returned an empty calendar β the bug this request arrived alongside
- Blue sky thinking
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96