ProcessInstance, ProcessWorkItem, auto field updates, and the day I learned why escalation rules save careers.
🏢 Once upon a time in a mid-sized company…
We had a purchase request system.
It looked fine on the surface — users could submit requests, data was stored, reports existed.
But there was one massive problem:
There was no approval system.
And that created chaos.
- Finance didn’t trust the numbers
- Department heads didn’t know what they were approving
- Requests were being edited after submission
- The CFO saw critical approvals 2 weeks late
That’s when I got the task:
“Build a structured L1 → L2 → L3 approval process.
With record locking, automated emails, escalation rules, and audit-ready tracking.”
This wasn’t just a feature.
It was a system that would define how decisions move inside the company.
Here’s exactly how I built it — step by step.
🔐 1. Record Locking & Unlocking (Data Integrity First)
❌ Problem
Users were editing records even after submitting them for approval.
That meant:
- Approvers were reviewing moving targets
- Decisions became invalid
- Audit trails were unreliable
✅ Solution
I implemented state-based record locking.
- When a record is submitted → it becomes read-only
- If rejected → it becomes editable again
- If fully approved → it is permanently locked
🧠 Logic
if(status == "Submitted" OR "L1 Approved" OR "L2 Approved")
isLocked = true
else
isLocked = false
💡 Insight
Locking isn’t just a feature — it’s what makes approvals trustworthy.
👥 2. Designing L1, L2, L3 Approval Levels
I mapped the approval hierarchy to real business roles:
| Level | Role | Responsibility |
|---|---|---|
| L1 | Department Head | Validate request legitimacy |
| L2 | COO | Operational approval |
| L3 | CFO | Financial approval |
Rules I enforced:
-
Each approver can:
- ✅ Approve
- ❌ Reject
- 💬 Add comments
❗ No one can modify previous decisions
🔄 Rejection at any level sends the request back to the requester
💡 Insight
Approval systems are not technical flows — they are organizational trust layers.
📧 3. Automated Email Notifications (Communication Layer)
Approvals fail without visibility.
So I built a trigger-based email system tied to each stage.
📌 Email Triggers
- On submission → notify L1
- On L1 approval → notify L2 + requester
- On L2 approval → notify L3
- On final approval → notify requester + finance
- On rejection → notify requester with reason + unlock info
📨 Example Email (L1 → L2)
Subject: L1 Approved — Pending COO Review
Hi [COO Name],
Request #[Record ID] has been approved by the Department Head.
Please review and take action before [Due Date].
Thanks,
System Automation
💡 Insight
Emails are not notifications.
They are decision triggers.
🔄 4. Automated Field Updates (State Management)
To keep everything trackable, I designed a clear status lifecycle.
🔁 Status Flow
Draft → Submitted → L1 Approved → L2 Approved → Final Approved
↓ ↓ ↓
Rejected Rejected Rejected
↓ ↓ ↓
Unlock Unlock Unlock
🧩 Fields I maintained
StatusLast_Approved_ByLast_Approved_DateNext_Approver_NameIs_Locked
Each approval step automatically updated these fields using Flows / Apex logic.
💡 Insight
If your status field is messy, your entire system becomes untraceable.
⏰ 5. Escalation Rules (The Real Game-Changer)
This is where the system went from “good” to “production-ready”.
🚨 Problem
Approvers delay.
Always.
✅ Solution: Time-based escalation
Rule 1:
- If L1 pending > 48 hours → Reassign to L2 → Send escalation email
Rule 2:
- If L2 pending > 24 hours → Notify CFO (view-only) → Send daily reminder to COO
🔥 Why this matters
- Prevents bottlenecks
- Removes dependency on manual follow-ups
- Keeps process moving without friction
💡 Insight
Escalation rules don’t just improve systems —
they protect deadlines, teams, and accountability.
🧠 6. The Core Engine: ProcessInstance & ProcessWorkItem
This is the part that separates beginners from real Salesforce developers.
📦 ProcessInstance
Represents the entire approval process for one record.
Stores:
- Overall status →
IN_PROGRESS,APPROVED,REJECTED - Start time / end time
- Current stage
🧩 ProcessWorkItem
Represents each approval step.
Stores:
- Assigned approver
- Step status (Pending / Approved / Rejected)
- Due date
- Escalation status
- Comments
🔍 Real-world query example
Find all L2 approvals that are pending and escalated
This is critical for:
- Dashboards
- Reporting
- Audits
💡 Insight
Without these objects, your system has no memory.
And without memory, there is no audit.
📊 7. Final Architecture Snapshot
User submits request
→ Lock record
→ Create ProcessInstance
→ Create ProcessWorkItem (L1)
→ Send email to L1
L1 approves
→ Update status
→ Create ProcessWorkItem (L2)
→ Notify L2
L2 approves
→ Update status
→ Create ProcessWorkItem (L3)
→ Notify L3
L3 approves
→ Final approval
→ Permanently lock record
→ Close ProcessInstance
→ Send final email
🧪 8. What I’d Improve Next Time
No system is perfect. Here’s what I’d upgrade:
- 🔀 Parallel approvals (e.g., L2 + Legal together)
- 🔔 24-hour reminders (not just escalation)
- ⚡ Store approval history as JSON for faster UI rendering
- 📊 Build dashboards for real-time approval tracking
✅ Key Lessons from This Build
- 🔐 Locking is non-negotiable for data integrity
- 📧 Emails without escalation = ignored notifications
- 🧠 ProcessInstance + ProcessWorkItem = audit backbone
- 🧪 Always test escalations using mock overdue scenarios
🔁 Final Thought
If you’ve ever built approval systems, you already know:
L1 is fast. L2 is busy. L3 is critical.
Each level behaves differently.
Your system must respect that.
🚀 Closing
This project wasn’t just about approvals.
It was about:
- Building trust in data
- Creating accountability
- Designing systems that work *without constant human follow-up
If you're building something similar or want to discuss architecture decisions, feel free to connect. Always happy to exchange ideas 🚀
Top comments (0)