Build an Approval Workflow App Your Team Will Actually Use
It is 4:40 on a Friday. A rep needs 22 percent off list to close a deal that has already slipped two quarters. She posts in the deal desk channel. The director asks what the account's renewal history looks like. She pastes a screenshot. Someone else asks whether finance already gave 18 percent to a similar account in March. Nobody knows. The thread goes quiet at 6:10 and the customer stops replying on Monday.
That is the reason companies build an approval workflow app, and it is almost never the reason they say out loud. The request had no home, the decision had no context, and the outcome left no trace anyone can find six months later.
Approvals are the most common internal tool in existence because every company has the same four verbs: someone requests, someone reviews, someone decides, and eventually someone audits. The verbs are simple. The tools built for them fail at a predictable rate, and they fail for reasons that have very little to do with the software and a lot to do with what got designed first.
What an approval workflow app actually has to do
Split the problem into the four verbs and be honest about where the difficulty sits.
Request. Capture enough detail to decide, and not one field more. The person filling this in is motivated: they want the thing. They will tolerate friction. This is the easy half.
Review. Put everything the approver needs in front of them at the moment of the decision, so they never open another tab. This is the hard half and it is where almost nobody spends their build time.
Decide. Record an outcome that downstream systems and humans can both trust: who, when, on what version of the request, with what reasoning.
Audit. Answer questions months later from people who were not in the room, using the values as they stood at the moment of the decision rather than the values today.
Most teams spend eighty percent of the build on the request form. That is backwards. The form is filled out once by someone who wants something. The review screen is used forty times a week by a director who does not want to be there. Optimize for the person who is bored, interrupted and slightly annoyed, because that person is the system's actual bottleneck.
The three failure modes that kill approval tools by week three
Context lives somewhere else. The approver sees "Discount request: 22 percent, Acme Corp, reason: competitive" and has to open the CRM to see deal size, the billing system to see payment history, and a spreadsheet to see what discounts got approved last quarter. Three tabs is where adoption dies. Approvers do not abandon tools because the tool is ugly. They abandon it because it makes them work harder than replying to a Slack DM.
The queue is invisible. Requests age silently. Nobody can see that eleven items are past two days old and four of them belong to the same approver who is on vacation. If the only view is a list sorted by creation date, the system has no operational awareness, and every escalation is a human noticing something by accident.
Decisions happen off the record. Someone approves in a DM, someone else forwards an email that says "fine by me", and the tool contains a subset of reality. Once the tool holds a subset, it is a reporting liability rather than a control, and everyone learns to route around it.
There is a fourth failure that is quieter and worse. Rubber stamping. If a rule routes a hundred requests and approves ninety nine, it is not a control, it is a tax on the ninety nine. Track approval rate per rule from day one. A rule that has never once produced a rejection should be deleted or converted into an automatic pass with a notification. Rejection rate, not volume, is the health metric of an approval system.
Decide where the records live before you design the approval workflow app
Every approval system has exactly one place that owns the row. Pick it deliberately at the start, because retrofitting the answer is a migration.
The useful rule: the record should live in the system that owns the consequence. Discount approvals belong next to the opportunity in the CRM, because the consequence is a quote and a contract. Purchase approvals belong next to the purchase order or invoice in the finance system, because the consequence is money leaving. Access requests belong next to the identity or ticketing system, because the consequence is a permission grant that a security reviewer will one day pull a report on. Content and campaign approvals belong next to the asset.
When you split ownership, you get drift. Two systems both think they know the status, and reconciliation becomes a recurring meeting.
This is also the point where you have to be honest about what a chat-built app can and cannot do. Skopx apps read from connected systems and take actions through connected tools: a table pulled with read-only SQL from your PostgreSQL or Snowflake instance, a list pulled from a connected tool, an action button that writes a decision back through that tool with an explicit click and a confirmation. What they do not do today is store their own records, and there is no form component that creates new data. So an app of this shape is a console, a review queue, an admin view over data that already exists somewhere. If your approval process needs the app itself to be the system of record that originates every request, that is not what this does, and any vendor telling you otherwise is selling you a migration you did not ask for.
In practice that limitation matters less than it sounds, because intake is the part you already solved. Requests are already arriving as Jira issues, Slack workflow submissions landing in a database, rows in an Airtable base, opportunities in HubSpot or Salesforce, invoices in QuickBooks or Stripe. The missing piece was almost never intake. It was a review surface with real context and audited actions on top of it.
Design the request step so it survives contact with real users
Three mandatory fields is a good ceiling: what is being asked for with its magnitude, why, and by when. Everything else should be derived or optional.
Prefill anything the system already knows. If the requester picks an account, the account owner, region, current contract value and renewal date should populate themselves. Every field a human retypes is a field that will eventually be wrong.
Do not let the requester choose the approver. They will choose the friendly one. Routing is derived from policy: amount bands, department, risk category. Two or three bands is usually right. Five bands means somebody is modeling an org chart instead of a decision.
Attach the artifact by link, not by upload. Uploaded copies go stale the moment the quote gets revised, and then you have approved a PDF nobody can find.
Freeze the request at submission. If the amount, scope or dates change after submission, the approval resets. This is the single most common audit finding in home-built approval tools: a request approved at 15 percent that shipped at 25 percent, because the record was mutable and nobody versioned it. If you take one structural decision from this article, take that one.
The review screen is the entire product
Sit with an approver for twenty minutes and write down every question they ask out loud. That list is your specification.
For a discount approval the list usually looks like: How big is the deal? What is the margin at this discount? What did we give this account last time? Has this rep been asking for exceptions all quarter? Is this customer paying us on time today? Who else has to sign off after me?
Every one of those questions is a component on the review screen. Deal size is a metric. Discount history is a small table. Rep exception rate is a stat. Payment status is a callout when it is bad and invisible when it is fine. The approval chain is a progress or timeline component, so the approver knows whether they are the last stop or the second of four.
Two details separate review screens that get used from ones that get abandoned.
The first is that the queue view and the detail view are different tools. The queue is scanned: five or six columns, sorted by risk and age, filterable by approver. The detail view is read: everything else lives there. Trying to make one screen do both produces a table with fourteen columns that nobody can read on a laptop.
The second is that column choice has to match the real shape of the data. If the justification field averages four hundred characters, it cannot be a table column, because truncation at forty characters turns it into noise. If ninety percent of requests are under a thousand dollars and three are over a hundred thousand, a plain sorted list buries the three that matter. Profile the actual values before you design the layout: column types, value ranges, text lengths, null rates. When Skopx builds an app it runs the query first and reads a profile of what actually came back, precisely because layouts designed against imagined data collapse against real data. You can do the same by hand in ten minutes with a few aggregate queries, and it is worth it every time.
The same discipline applies to any queue-shaped tool. The reasoning behind column selection and aging buckets carries over directly from a support dashboard or an operations dashboard, where the job is also to make the exception visible without hiding the baseline.
Decide: one click, one reason, one irreversible trail
Approval is a write, and writes have failure modes that demos never show you.
Handle the double approval race. Two approvers open the queue at the same time and both click. Your update must be conditional, along the lines of setting status to approved only where status is currently pending, and the second click has to fail loudly rather than overwrite. If the action fires a side effect like issuing a purchase order, an unconditional write means two POs.
Confirm anything irreversible. A confirmation dialog that names the specific object and amount, not a generic "Are you sure?". Approvers click through generic dialogs without reading in about a week.
Require a reason on rejection. Optional on approval, but keep the field, because approval notes are what make an audit painless later. "Approved, one time exception, competitive displacement at Acme" is worth its weight two quarters on.
Enforce segregation of duties in code, not in policy. The requester cannot approve their own request. Somebody will try, usually a manager filing on behalf of their team, and a policy document does not stop it.
Record delegation as delegation. If the VP is out and the director decides, the record says "approved by the director on behalf of the VP", not "approved by the director". Auditors care about that distinction and reconstructing it later is miserable.
Keep the decision and its side effect separate and explicit. If approving automatically provisions access or releases a payment, an accidental click costs real money. One click, one confirmation, one named action.
Escalation, delegation and the out of office problem
Approvals do not fail at the decision. They fail in the waiting.
Set a target time to decision per request type, measured in business hours, and make aging visible in the queue with buckets rather than raw timestamps: fresh, aging, breached. Humans respond to three colors, not to a date column.
Send a reminder to the approver at some fraction of the target and escalate to their manager at the target. Escalation means the request appears in a second person's queue, not that it disappears from the first. Never auto-approve on timeout. Auto-approval on timeout is how a control becomes theater. If a class of request truly can pass without review, pass it at intake and log it, do not pretend to review it and then let the clock decide.
Reminders and escalations belong on a scheduler rather than inside the review screen, since the screen only runs when someone opens it. A scheduled workflow that runs every hour, checks aging requests and posts to Slack is the right shape for that half of the system, with run history so you can prove the nudge actually went out.
Delegation needs a real setting, not a convention. If the only way to handle a two week vacation is for the approver to hand over their password, you have built a security incident with a queue attached.
Choosing the build surface for your approval workflow app
There is no universally correct answer here. There is a correct answer for your data, your team and how many of these you plan to run.
| Approach | Where the request record lives | Where it breaks first | Best when |
|---|---|---|---|
| Email plus a spreadsheet | Nowhere durable. The sheet is a transcript someone maintains by hand | Two people reply on the same thread with different answers, and nobody can prove which version of the request was approved | Under five requests a month with a single approver |
| Slack or Teams workflow writing to Sheets or a database | The row the workflow writes | Context. Approvers decide from a four field message with no history attached, and the thread scrolls away by lunch | Intake is the real bottleneck and the decisions are genuinely simple |
| Ticketing tool such as Jira Service Management, Zendesk or Freshservice | The issue or ticket | Cross-system context. Ticket fields cannot show margin from the CRM or spend against budget in the ledger without custom work | IT and internal service requests, where the ticket already is the object being approved |
| Native approvals inside the system of record such as Salesforce, NetSuite, Coupa or Workday | The object being approved | Anything outside that system. Multi-system approvals get bolted on with integrations that quietly drift | The decision concerns one object inside one platform you already run and administer |
| Low-code builder such as Retool, Budibase, ToolJet or Appsmith | A table you create and own | Ownership over time. Someone has to maintain queries, permissions and deploys, and that someone changes jobs | You need custom state and forms that create records, and you have engineering capacity to maintain them |
| Chat-built review console over records that already exist, the shape Skopx apps take | Whatever system already owns the row. The console never owns it | Any process that needs the console itself to originate records, since these apps read and act but do not store their own data | The data already exists across tools and the missing piece is a review surface with context and audited actions |
The row most teams should read twice is the fourth one. If you run Salesforce and the approval is about an opportunity, use native approvals. If you live in Jira Service Management and the request is an access ticket, use the built-in workflow. Native approvals sit next to the object, inherit its permissions, and survive your departure. Building a parallel system next to a capable native one is the most common self-inflicted wound in this category.
Build something custom when the decision genuinely spans systems, when the context an approver needs lives in three places, or when the native workflow engine cannot express your routing without a consultant. If you are weighing the maintained-code route, the tradeoffs are covered directly in Retool versus Appsmith and in the survey of Budibase alternatives. If your approvals are mostly about money, the reporting side of the problem overlaps heavily with a financial reporting app.
Audit: what you will be asked for six months later
The questions arrive in a predictable order, usually from someone with a deadline.
Who approved this, and did they have the authority at the time? Store the approver's role as of the decision, not their current role. People move.
What did the request say when it was approved? Snapshot the fields at decision time. If the record is mutable and you only store a pointer, you cannot answer this, and the honest answer is that you do not know.
What policy applied? Rules change. Version them, and store the version identifier on the decision. Without it you will be explaining that the threshold was different in March and hoping someone believes you.
Who could have approved but did not? Useful for both audit and process improvement, and cheap to store if you capture the eligible approver set at routing time.
Make the log append only. An approval history that can be edited is not evidence. And decide retention deliberately: most finance and access approvals want years, most marketing approvals want months.
Rolling it out so people actually use it
Start with exactly one approval type. Not a platform, one type. The discount approval, or the spend request over a threshold, or the access grant.
Build for the approvers first. Five people decide, two hundred people ask. If the five have a screen that answers their questions in one place, adoption takes care of itself, because they will start telling requesters to use it.
Deliver the queue where the approver already is. A chat notification with a link into the item is worth more than any amount of internal enablement, because you are borrowing a habit rather than asking for a new one. A browser side panel that sits beside the CRM does the same job.
Run a two week pilot and measure exactly two things: time to decision and rejection rate. If time to decision drops and rejection rate goes to zero, you did not build a control, you built a formality, and it is time to raise the threshold. If rejection rate is healthy but time to decision is worse than the old email chain, the review screen is missing context.
Then kill the old path on a stated date. Parallel paths always resolve in favor of the lazier one, and the lazier one is always the DM.
FAQ: the questions that come up in every approval build
How fast should an approval actually be?
Set the target from the consequence of waiting, not from a benchmark. A discount approval that gates a signature this week needs hours. An annual vendor renewal can take a week without harming anything. Write the target down per request type, measure against it in business hours, and revisit after a month. The number matters less than the fact that a number exists and someone sees the breaches.
Can Skopx build an approval workflow app end to end?
It can build the review, decide and audit surfaces over requests that already live in a connected system: the queue with filters and aging, the context an approver needs, action buttons that write the decision back through the connected tool with an explicit click and a confirmation, and the app shared with your organization or kept private. It cannot be the system of record for the requests themselves, because these apps do not store their own data and there is no component that creates new records. If intake already happens in a ticket, a form that writes to a table, or a CRM object, that gap is not a problem. If you need the app to originate and own every request, you need a different tool for that half.
Should approvers decide inside chat or inside a queue?
Notify in chat, decide in the queue, with one exception. Genuinely binary, low value, high volume decisions can be resolved with buttons in a chat message, and people love it. Anything that requires context should open the item, because a chat card cannot show payment history and margin without becoming a wall of text nobody reads. The mistake is not choosing chat. The mistake is letting chat approvals happen in threads that never write anything back to the record.
How do I stop approvers from rubber stamping?
Reduce their volume and raise their information. Delete rules with no rejections in the last quarter, raise thresholds until the queue is small enough to think about, and flag the exceptions explicitly so the unusual item looks different from the routine one. An approver seeing eight requests a week with the outliers marked makes real decisions. An approver seeing eighty makes none.
What do auditors actually ask for?
A list of approvals in a period with approver, timestamp and amount. Evidence that the approver had authority at that time. Proof that the request was not modified after approval. Exceptions and who granted them. If your system can export those four things without a human reconstructing anything, the audit is a morning of work rather than a fortnight.
The short version
The approval workflow app that survives is the one that answers the approver's questions on one screen, records decisions against a frozen version of the request, makes aging visible, and lives next to the system that owns the consequence.
Decide where the record lives before you build anything. Then spend your effort on the review screen, because the form is not what people abandon.
Skopx Team
The Skopx engineering and product team