Blog › ICP guides
Lotus Notes developer on retainer: NotesDocument.Save silent failure, HCL Notes developer, Domino developer on monthly retainer
October 9, 2026 · ~15 min read
A LotusScript developer was maintaining a multi-stage approval workflow application in Lotus Notes 9 for a regional financial services firm. The workflow routed expense report documents through three approval stages — manager approval, finance review, and controller sign-off — with a LotusScript action button handler on each stage that read the current document from a NotesView search result, updated the status field, and called doc.Save(False, True) to write the updated document back to the NSF database before sending the approval email notification via NotesMailMessage. The developer had tested the workflow thoroughly with a single-user session: each approval stage worked correctly, the status field advanced as expected, and the approval notification emails arrived correctly. In production, the finance department had three reviewers who each handled the finance-review stage; on busy days, two reviewers would open and approve different expense reports simultaneously. On three separate occasions over two months, an expense report document showed “Finance Approved” in the email notification but remained in “Pending Finance Review” status in the view. The controller’s dashboard showed the document as awaiting finance approval; the finance reviewer’s email showed it as approved. Finance team discovered the discrepancy when the controller queried the pending queue. Workflow documents in incorrect state per month: 3–4 → 0 after adding explicit Save return value checking with retry and failure notification.
The root cause was NotesDocument.Save returning False silently when a save conflict prevented the write. In Lotus Notes, a save conflict occurs when two users (or two agents) modify and attempt to save the same document simultaneously: the second save attempt detects that the document’s timestamp has changed since it was opened and returns False rather than overwriting the first user’s changes. Critically, NotesDocument.Save returns a Boolean — True on success, False on failure — and does not raise a LotusScript exception on failure. This means that a LotusScript On Error handler does not fire when Save returns False; the agent continues executing the statements after the Save call as if the save had succeeded. In the financial services firm’s approval workflow, when the second reviewer’s action button agent called doc.Save(False, True) while another reviewer’s agent had the same document locked (from opening it via a NotesView search at the same millisecond), Save returned False; the agent continued to the NotesMailMessage.Send call and sent the approval email; the document was never actually saved to the NSF. Fix: check the Save return value immediately after the call; retry once with a 500-millisecond delay if it returns False; if the retry also returns False, display a MsgBox notification to the reviewer and exit the agent without sending the email notification.
The LotusScript NotesDocument.Save(force, makeresponse) method signature exposes both failure modes as parameters, not exceptions. The first parameter, force (Boolean), controls behavior on save conflict: False means do not force save — if a conflict is detected, return False and do not overwrite; True means force save — overwrite even if a conflict is detected (creates a “Save Conflict” document in the database instead of failing silently). The second parameter, makeresponse (Boolean), controls whether the saved document is created as a response to its parent document. In the financial services firm’s approval workflow, the action button handler called doc.Save(False, True) — force = False (meaning “do not overwrite on conflict, return False instead”) and makeresponse = True (correct, since expense report approvals are response documents). The correct pattern is: Dim bSaved As Boolean; bSaved = doc.Save(False, True); If bSaved = False Then; retry or notify. Calling doc.Save(True, True) would force the save but risk overwriting concurrent changes; the correct solution for workflow applications is doc.Save(False, True) with explicit return-value handling, not a forced overwrite.
Lotus Notes (now HCL Notes) is IBM’s document-oriented collaborative application platform, built around the NSF (Notes Storage Facility) database format. Each NSF is a document store: documents are the fundamental unit of data storage, each document containing a collection of typed items (fields). Unlike relational databases, NSF documents do not have a fixed schema: each document can have any set of items, typed as text, number, date/time, rich text, names, or reader/author lists. The NSF format uses a proprietary B-tree storage engine with replication as a first-class feature: NSF databases replicate peer-to-peer across Domino servers and Notes clients, with conflict resolution handled by timestamp comparison and the save conflict document mechanism. Replication makes Notes applications resilient to network partitions (offline editing with later sync) but introduces the save conflict risk: two users editing the same document in separate replicas, or two users editing the same document on the same server within the same second, produce a save conflict.
LotusScript, NotesDocument, and workflow agent patterns on Domino
LotusScript is IBM’s BASIC-derivative object-oriented scripting language for Notes/Domino application development. LotusScript code runs in three contexts: client-side agents (triggered by a user clicking an action button or running an agent manually in the Notes client); server-side scheduled agents (run by the Domino server on a schedule or triggered by a Notes event, running under the agent signer’s identity); and web agents (triggered by HTTP requests to the Domino server when running in a web application context). The Notes Object Model provides the NotesSession, NotesDatabase, NotesView, NotesDocument, NotesItem, NotesRichTextItem, and NotesMailMessage class hierarchy. NotesSession.CurrentDatabase returns the database containing the currently executing code. NotesView.GetDocumentByKey(key, exact) returns the first document in a view whose first sort column matches key. NotesDocument.GetItemValue(itemName) returns the value of a named field as a Variant array. NotesDocument.ReplaceItemValue(itemName, value) updates a field value in memory (the change is not persisted until Save is called).
The save conflict in Notes arises from the NSF’s optimistic concurrency model. Notes does not use database row locks for document editing: when a user opens a document in edit mode in the Notes client, no server-side lock is acquired; the document is simply read and displayed. If two users open the same document in edit mode simultaneously and both click the Save action, the second save attempt compares the document’s current $Revisions item (which tracks the edit timestamps) against the server’s current copy; if the server copy has been modified since the client opened the document, Notes detects a conflict. In a standard Notes form, the conflict is handled by creating a save conflict document — a special response document in the NSF marked with the $Conflict field, visible in views as a conflict indicator; a Notes administrator or the document owner must manually resolve the conflict by merging the two versions. In a LotusScript action button or agent, the conflict returns False from Save(False, ...) — no conflict document is created, no exception is raised. The agent developer must handle this case explicitly.
LotusScript’s error handling model separates exceptions (raised with the Error statement or by the runtime on invalid operations) from method return values. On Error GoTo label (or On Error Resume Next) sets a trap for LotusScript runtime exceptions; it does not intercept non-exception return values. NotesDocument.Save failing returns False — it is not a runtime exception, so On Error does not trigger. This is a design choice consistent with the original Lotus Notes Notes API: methods that can fail non-fatally return status values rather than raising exceptions. The pattern is widespread in the Notes Object Model: NotesDatabase.Open(server, path) returns False if the database cannot be opened; NotesView.GetDocumentByKey returns Nothing if no match is found; NotesDocument.Send(attachForm) returns True or False depending on whether the send succeeded. A LotusScript agent that does not explicitly check each return value and act on failures is implicitly assuming all operations succeed — an assumption that holds in single-user testing but fails under concurrent production load.
HCL Technologies acquired the IBM Collaboration Solutions (ICS) portfolio — including Notes, Domino, Connections, Sametime, and Verse — in December 2018. HCL has continued active development: HCL Notes/Domino 10.0 (2018), 11.0 (2020), 12.0 (2021), and 14.0 (2023, skipping version 13 for superstition reasons consistent with HCL’s customer base). HCL Nomad Web allows Notes applications to run in a browser without the Notes client; HCL Verse provides a modern email interface on Domino; HCL Leap (formerly Domino Volt) provides a low-code form builder on Domino. Notes 9.0 Social Edition (IBM’s final version before HCL acquisition) remains widely deployed at financial services firms, law firms, healthcare organizations, government agencies, and insurance companies that built document workflow applications on Notes in the 1990s and 2000s. The NSF database format is binary-compatible across versions from Notes 4.x through HCL Notes 14.x; an NSF created in Notes 4.5 in 1995 opens without conversion in HCL Notes 14.0 today, which means the LotusScript agents written in the 1990s — before the Save return-value pattern was understood — are still running unmodified in production at those organizations.
Typical Lotus Notes developer retainer work and what it looks like in a work log
NotesDocument.Save returning False with workflow notification already sent is the canonical Lotus Notes invisible workflow integrity bug. The pattern is consistent: a LotusScript action button handler opens a document from a view, updates a status field with doc.ReplaceItemValue("Status", "Finance Approved"), calls doc.Save(False, True) without checking the return value, then calls mailMsg.Send(False, recipients). In a single-user test, Save always returns True and the workflow advances correctly. In production, two reviewers handle the same document class simultaneously; one reviewer’s Save succeeds (returns True, document saved); the second reviewer’s Save returns False (save conflict) but the agent continues and sends the approval email. The workflow system now shows the document in two inconsistent states: the email says “Finance Approved,” the view says “Pending Finance Review.” The fix: bSaved = doc.Save(False, True); If Not bSaved Then; retry once after 500ms; If Not bSaved Then MsgBox "Save failed: document modified by another user. Please reload and re-approve." : Exit Sub; send the notification email only after a confirmed True return. Workflow documents in incorrect state per month: 3–4 → 0. Work log: “HR_Approvals.nsf; ApproveFinance LotusScript action button handler in ExpenseApprovalForm; doc.Save(False, True) return value unchecked; concurrent second-reviewer Save returns False (save conflict); notification email sent before confirming save; workflow document state inconsistent (email: Approved, view: Pending); fix: check Save return, retry once, notify-and-exit on second False; incorrect-state documents: 3–4/month → 0; 2h.”
Scheduled Domino agent silently failing due to wrong signer identity is the second most common Lotus Notes retainer pattern in enterprise deployments. A LotusScript scheduled agent runs nightly on the Domino server to purge expired expense report documents by setting their status to “Archived” and moving them to an archive NSF. The agent was developed and signed by a developer with full Editor access to both the source and archive NSF databases. The developer left the organization; their Notes ID was removed from the Domino ID vault; the scheduled agent’s signer identity now resolves to a deleted user. Domino runs the agent under the signer’s effective ACL level — which for a deleted signer is no access. The agent executes silently (no error is raised to the server console by default), accesses the source database using the signer identity, finds it has no access, and exits without processing any documents. Expired expense reports accumulate. No error appears in the Notes agent log unless the agent explicitly calls Print "Purge agent: found " & nDocs & " expired documents" to produce a log entry that can be reviewed in the Notes log database (log.nsf). Fix: re-sign the agent with a service account Notes ID that has the correct ACL level on both databases; verify by running the agent manually from the Notes Designer and checking the agent log. Expired documents purged per nightly run: 0 → 45–50 as expected. Work log: “HR_Approvals.nsf; PurgeExpiredReports scheduled LotusScript agent; signer identity (DeveloperName/OrgName) removed from ID vault; agent runs silently with no-access to database; expired documents accumulate (180 over 4 months); fix: re-signed with ServiceAccount/OrgName (Editor access on both NSFs); agent log confirms 45–50 purges per nightly run; 1h.”
Computed field formula and LotusScript agent operating on same field with inconsistent type assumptions is the third common Lotus Notes retainer pattern. A Notes form has a computed field TotalExpenses whose formula is @Sum(LineAmounts) — a Notes Formula language sum that returns a Number. A LotusScript action button handler reads the field with doc.GetItemValue("TotalExpenses")(0) and assigns it to a Dim total As Double variable; for documents where no line amounts have been entered yet, GetItemValue("TotalExpenses") returns an empty Variant array; (0) on an empty array raises a LotusScript runtime exception “Array index out of bounds” — which, if the On Error handler uses On Error Resume Next, is silently swallowed; total remains 0.0; the validation that should prevent submission of zero-dollar expense reports silently passes; an expense report with no line items is submitted for approval. Fix: check UBound(doc.GetItemValue("TotalExpenses")) >= 0 before indexing; if the array is empty, treat the total as 0 and fail validation explicitly. Zero-dollar expense reports submitted for approval per month: 2–3 → 0. Work log: “HR_Approvals.nsf; SubmitExpenseReport LotusScript action button; doc.GetItemValue("TotalExpenses")(0) on empty array; On Error Resume Next swallows array-index exception; total = 0 silently; zero-dollar reports pass validation; fix: UBound check before array index; zero-dollar submissions: 2–3/month → 0; 1.5h.”
Track Lotus Notes developer retainer hours without the status emails
When a 2-hour investigation traces 3–4 workflow documents in incorrect state per month to NotesDocument.Save returning False without raising an exception — two concurrent reviewers; second reviewer’s Save returns False (save conflict); agent continues to send approval email without confirming save; workflow state inconsistent; fix: check Save return value, retry once, notify-and-exit on second False — the work log must name the NSF database, the LotusScript agent or action button handler, the Save call, the concurrent condition, and the workflow documents in incorrect state before and after. HourTab gives your Lotus Notes retainer client a public dashboard URL they can bookmark: hours used, hours remaining, and a work log naming the save-conflict retry fix. No client login. No status emails. CSV in, URL out.
How HourTab tracks Lotus Notes developer retainer hours
Lotus Notes NotesDocument.Save silent failure bugs are invisible by the same mechanism that makes them impossible to reproduce in single-user development testing: when one user tests the approval workflow, Save(False, True) always returns True because no other session has the document open simultaneously; the status field updates correctly; the approval email arrives correctly; the workflow advances to the next stage correctly. The developer confirms the workflow works and moves on. The only evidence of the problem surfaces when two production users handle the same document type simultaneously — with no LotusScript exception (the On Error handler never fires), no Notes error dialog to the user (the action button agent runs in the background), no visible indicator on the document that the save failed, and no server console error (Domino does not log NotesDocument.Save returning False to the console by default). The discrepancy is discovered by the document owner or workflow administrator when the status in the view does not match the email notification, or when a document gets stuck in an intermediate state and the downstream workflow stage cannot find it in the expected view.
The work log must name the mechanism to be auditable: which Notes database (HR_Approvals.nsf on server DOMINO01/Corp), which LotusScript agent or action button handler (ApproveFinance action button handler in ExpenseApprovalForm), the Save call (doc.Save(False, True)), the concurrent condition that triggers the False return (second reviewer opens and saves the same document class within the same second), the workflow documents in incorrect state per month before fix (3–4), the documents in incorrect state after fix (0), and the fix (check Save return value; retry once after 500ms delay; display MsgBox and exit without sending notification email if retry also returns False). A log entry that says “fixed approval workflow issue, 2h” is not auditable. A log entry that names the NSF, the action button, the Save(False, True) return value, the silent failure mechanism, and the retry-and-notify fix is auditable and defensible to the compliance team at a financial services firm or law firm. HourTab gives Lotus Notes developers a public retainer-hours URL they send to clients — financial services firms, insurance companies, law firms, healthcare organizations, and government agencies that built document workflow and compliance applications on Lotus Notes in the 1990s and 2000s, maintained today by the original LotusScript developer or a successor retainer consultant.
Comparative context: Lotus Notes NotesDocument.Save returning False without raising an exception is a specific instance of the broader pattern where a platform’s write operation signals failure via a return value rather than an exception, and the developer assumes success without checking. Progress 4GL retainers cover a related pattern: FIND FIRST Customer EXCLUSIVE-LOCK NO-WAIT succeeds (no error) even when the lock cannot be acquired when using NO-WAIT — the “success” is a no-op lock acquisition that must be checked with LOCKED(Customer). Natural/Adabas retainers cover the end-of-program hold queue release: UPDATE (ISN) adds ISNs to Adabas hold queue; END TRANSACTION is required to release them; END-PROGRAM alone does not release. Both share the “tested solo, works correctly; fails with concurrent sessions” diagnostic pattern. OpenRPT retainers cover PostgreSQL FOR UPDATE cursor lock-hold through the rendering phase — same invisible concurrent-user lock class, where the individual developer session never experiences the contention that production concurrent sessions encounter.
FAQ: Lotus Notes developer retainers
What does a Lotus Notes developer on retainer typically do?
A Lotus Notes developer on monthly retainer covers NotesDocument.Save return value audits (reviewing every LotusScript agent that calls doc.Save and confirming the Boolean return is explicitly checked); save conflict diagnosis (correlating Domino server logs and agent log entries to identify the database, form, and concurrent user that triggered a silent False return); workflow state machine audits (tracing LotusScript action button handlers for missing Save-before-notification guards); Domino agent scheduling and security context review (signer identity, server ACL levels); Notes database schema migration and design refresh; and LotusScript-to-Formula cross-reference validation for computed fields operated on by agents.
What Lotus Notes workflow bug work is most commonly underlogged?
NotesDocument.Save returning False with no exception when a save conflict occurs — where a LotusScript workflow agent calls doc.Save(False, True) on a document that another Notes user has open, the Save returns False (not an exception, so On Error does not fire), the agent continues and sends the approval email, but the document was never actually saved — is the most systematically underlogged Lotus Notes retainer work. The developer who tests with a single session never observes the save conflict; the discrepancy is discovered only when the workflow email says “Approved” but the document view still shows “Pending.” The work log must name the NSF, the agent or action button, the Save call, the concurrent condition, and the workflow documents in incorrect state before and after the fix.
What are typical Lotus Notes developer retainer rates?
Entry-level Lotus Notes developers with experience in LotusScript fundamentals, Notes Formula language, and basic form and view design typically bill at $65 to $110 per hour. Mid-level Lotus Notes developers with experience in LotusScript workflow automation, Notes agent scheduling and security context, Domino server administration basics, and save conflict detection and retry logic typically bill at $95 to $165 per hour. Senior Lotus Notes developers with deep knowledge of Notes 8.5/9.0, HCL Notes 10.x/11.x/12.x, Domino server architecture, XPages development, and production forensics on enterprise document workflow systems typically bill at $140 to $250 per hour. Monthly retainer ranges: $1,500 to $3,000 per month for advisory engagements; $2,500 to $5,000 per month for active maintenance of enterprise document routing and compliance workflow databases.
What should a Lotus Notes developer retainer agreement include?
A Lotus Notes developer retainer agreement should specify: Notes/Domino version (Notes 8.5, 9.0, HCL Notes 10.x, 11.x, or 12.x); whether the retainer developer has Notes Designer access to the NSF design (required to edit LotusScript agents and action buttons); Domino server access level for log analysis and replica management; whether the retainer covers concurrent-user save conflict scenarios; whether the retainer includes Domino server log and Notes agent log review; whether the retainer covers XPages or classic Notes client interface; and whether the retainer includes migration planning to HCL Nomad Web, Domino Volt, or other modern platforms.
How should Lotus Notes developer retainer hours be logged?
Log each Notes retainer session with the NSF database, the LotusScript agent or action button, the Save call, and the concurrent condition. For NotesDocument.Save silent failure: database (HR_Approvals.nsf), handler (ApproveFinance action button in ExpenseApprovalForm), Save call (doc.Save(False, True)), concurrent condition (second reviewer saves same document simultaneously), Save return value before fix (False, undetected), workflow documents in incorrect state per month before fix (3–4), fix (check Save return; retry once after 500ms; MsgBox and exit on second False), incorrect-state documents after fix (0), hours (2h). For scheduled agent signer identity: database, agent name, signer identity, required ACL level, actual entry found, fix (re-signed with service account), hours (1h). For computed field type mismatch: database, form, field name, formula type, LotusScript assumption, fix (UBound check before array index), hours.