Nonprofit

Case study · Illustrative scenario

Scaling Salesforce Automation Without Creating More Technical Debt

A two-person admin team that said yes to every small, reasonable automation request, and found four years later that nobody could safely change anything on the Contact object.

Machined steel gears meshed together in close-upNonprofit
Before and afterAutomation on one object, before and after conventions

Before, eleven record-triggered flows ran on the Contact object across both save timings in an order nobody had chosen, none of them named to a convention, several with no entry criteria at all, and failures visible only to whoever happened to read the automation error emails. After, one flow per object per timing carries the logic, every flow name encodes object then timing then purpose, entry criteria state what changed rather than what the record looks like, and a fault path writes a visible exception record that appears on a queue somebody owns. The count fell, but the change that mattered was that the order and the failures both became things a person can read.

  1. Before

    • Eleven record-triggered flows on Contact
    • Both save timings, order never set
    • No naming convention, several ending in v2 or Final
    • Four flows with no entry criteria
    • Two subflows with no owner and no caller list
    • Failures sent to an unread automation error address

    Each flow was a reasonable answer to one request. Nothing here was a mistake at the time it was built.

  2. After

    • One record-triggered flow per object per timing
    • Explicit ordering wherever a split was deliberate
    • Names encoding object, then timing, then purpose
    • Entry criteria stated as what changed
    • One subflow, with a named owner and a stated contract
    • A fault path that raises a visible exception record on an owned queue

    The target state is not fewer flows. It is that every flow that exists was chosen, and every failure reaches a person.

A worked scenario showing how we approach this problem. The architecture and decisions are our real practice; it is not an account of one named customer.

The problem, as it actually presented

Nobody asks for help with technical debt. The request that arrives is a change that should take an hour and has taken three weeks, or a defect that two people have looked at and neither can explain.

In this shape the org is four years old and was set up well. Programme delivery, grant management and supporter fundraising all run on it. The admin team is two people, one of them part time, and between them they have said yes to a few hundred requests, almost all of which were small and almost all of which were reasonable.

That is how the Contact object ends up with eleven record-triggered flows. One sets a programme eligibility flag, one writes a household roll-up, one stamps a consent date, one notifies a programme lead. Each was the right answer to the question asked that week, and adding a new flow was always the lower-risk option, because opening the existing one meant understanding logic somebody else wrote for a reason nobody had recorded.

The compounding effects are not subtle once you look. Two of the eleven set the same field under conditions that overlap, so the winning value depends on which ran last. Four have no entry criteria at all, so they are evaluated on every save of a Contact, including the nightly integration load. Several are named for the ticket that produced them, so the flow list reads as a list of ticket numbers. Two subflows exist that nobody can name an owner for. And the automation failures go to a shared address that stopped being read after the person who set it up moved teams.

What we designed, and why

The instinct in the room was a rewrite. We argued against it, because the logic in these flows was mostly correct and had been quietly getting the right answer for years. Throwing it away would mean re-proving every business rule with a team that has no capacity to re-prove anything. The work is conventions applied to what exists, not a new estate.

The ordering is the risk, not the count

Where more than one automation runs on an object at the same timing, an execution order exists. The only question is whether it is one somebody chose.

Nobody had chosen this one. It had held for years, which is precisely what made it dangerous: the org had accumulated logic that depended on it, none of the dependencies were recorded, and the failure mode is a wrong value appearing after a release or a data load with nothing in the org having visibly changed.

So the design target is one record-triggered flow per object per timing, applied to the two busiest objects and left alone everywhere else. Where a split stays, it stays because somebody named a reason, and the ordering is written on both flows rather than inferred.

We also removed dependencies rather than recording them where we could. Logic that reads a value another automation has just written is fragile in a way that logic deriving the value itself is not.

Names that survive the person who wrote them

The flow list is the only navigable surface an admin has over the estate, and it is a list of names. Anything a name does not say has to be discovered by opening the flow.

The convention encodes what the flow acts on, then when it runs, then what it is for, in that order, because the list sorts alphabetically and clustering by object is what makes elimination possible. Ticket numbers came out. Version numbers came out, because the platform already versions flows and a name carrying Final describes build history rather than behaviour.

The convention goes in the build template and the definition of done, not in a review step. Reviews get skipped when a grant reporting deadline lands, and nobody goes back to rename afterwards.

Entry criteria, so a flow does not run on every save

Four flows had no entry criteria and instead opened with a decision element that routed most records straight to the end. That flow still ran. The transaction still paid for considering it.

Two changes fix the class. Conditions move into the entry criteria, which are evaluated before the flow is entered at all, rather than into the first decision element, which is evaluated after everything done to reach it. And conditions are stated as what changed rather than as what the record now looks like, because a condition describing current state fires on every save that leaves the record in that state, including the integration writes that have nothing to do with the process.

When a subflow is reuse, and when it is indirection

Two subflows existed. One was genuinely one thing: an address standardisation routine used by several processes, with a stable contract and an obvious business owner. That one stayed, and acquired the thing it had been missing, which is a named accountable role and a maintained list of callers.

The other was three similar things that had been merged because they looked alike, and had since grown branches for each caller. Every change to it required understanding all three callers from inside a component that could see none of them. We split it back into duplicated logic in the three places.

That reads as a regression and is not. Duplicated logic drifts, which is a visible and slow cost. An unowned shared component fails invisibly and everywhere at once, and a two-person team has no capacity to absorb the second failure mode.

Failures that reach a person

The fault paths were the smallest change and the one the team noticed most.

A create, an update or an action can fail for reasons the flow did not cause: a validation rule fires, a required field is blank on a record the flow is assembling, a service is unavailable. Without a fault path the flow stops where it stopped, and whatever was committed before that point stays committed. A parent updated and the child never created. A status advanced and the programme lead never notified.

So every element that can fail gets a fault path, and the fault path does two things. It writes an exception record carrying the record identifier, the flow, and what failed, onto a queue with an owner and a weekly review. And it leaves the data in a state somebody can reason about, which sometimes means reversing the earlier step and sometimes means flagging the record, and never means continuing silently.

An email address nobody reads is not error handling. It is the appearance of error handling, which is worse, because it stops anybody asking the question again.

Implementation

The sequence is chosen so that a two-person team can stop after any stage and still be better off.

Audit first, and make it a list rather than a document. Every record-triggered flow by object and timing. Wherever two appear at the same timing you have an ordering dependency nobody chose, and that list is the work queue in priority order.

Then the naming pass. It changes no behaviour, which makes it the safest work in the programme and the one that makes everything after it cheaper. Rename, and write the four description lines while you are in there: why it exists and the ticket or policy behind it, the accountable role, what it acts on and explicitly what it does not, and what else runs on the same object at the same timing.

Then consolidation, on the two busiest objects only. Not the whole org. The objects where the ordering risk is real, one at a time, with the fixtures from the audit used to confirm behaviour did not change.

Then entry criteria, then fault paths. Fault paths last because they are the one stage that adds new records and a new review habit, and a habit introduced during a migration does not survive it.

Configuration that the business changes, such as programme eligibility thresholds, moved into custom metadata rather than staying hardcoded in decision elements. That single change converts a class of request from a flow modification into a data edit, which for a two-person team is the difference between a deployment and a Tuesday.

The decisions that were contested

Not rewriting. There was appetite for a clean rebuild and it was the more satisfying option. We argued against it on capacity grounds: a rebuild means re-establishing every business rule with the two people who are also running the ticket queue, and the rules were not the problem. The order, the names and the silence were.

Splitting a subflow back into duplicated logic. This was the hardest conversation, because it looks like undoing good practice. The position we held is that reuse without an owner is not reuse, and the team could not name an owner for that component or reconstruct its callers without opening every flow in the org.

Leaving nine objects alone. The tidier programme applies the convention everywhere. We scoped it to the two objects where the ordering risk was real and left the rest with a naming pass only, because a convention adopted on two objects and held is worth more than one adopted on eleven and abandoned in March.

Booking deletions rather than deactivating. Three flows were switched off years ago by people who have left. Deactivating again would have added to the graveyard. Each got a stated window, a named watcher and a booked deletion date, which turns an argument about risk into an experiment with a deadline.

What changes

The outcomes worth claiming are operational, and they follow from the conventions rather than from effort.

BeforeAfterWhat made the difference
Eleven flows on one object, order never chosenOne flow per object per timing, splits documented with explicit orderThe dependency became something somebody decided rather than inherited
Flow names carrying ticket numbers and FinalNames encoding object, timing and purposeThe list became searchable by elimination, so most flows never get opened
Four flows evaluated on every saveConditions in the entry criteria, stated as what changedEvaluation moved ahead of entry rather than after it
Two subflows, neither with an ownerOne subflow with a named role and a caller listReuse now has an accountable owner, and the rest is visible duplication
Failures sent to an unread addressA fault path raising an exception record on an owned queueA failure now produces something with a record identifier on it
Eligibility thresholds hardcoded in decisionsThresholds in custom metadataA business change stopped being a deployment

The second-order effect is the one the team noticed. Change requests started being estimated as changes again, because the question of what else touches this field now has an answer that can be read out of the org rather than reconstructed.

What we would tell you before starting

Start with the audit and resist starting with the rewrite. The list of objects carrying more than one flow at the same timing takes an afternoon to produce and it will reorder your priorities, because the objects that feel worst are rarely the objects where the ordering risk is real.

Scope it smaller than feels right. A convention held on two objects compounds. A convention announced across the org and abandoned when the next deadline lands teaches the team that conventions do not survive contact with real work, and that lesson is expensive to unteach.

And be honest about the constraint. With two people, the maintainable option beats the clever one every time, and that is a design principle rather than an apology. Duplicated logic somebody can read, a flow named so it can be eliminated from a search, and a failure that lands on a queue are all worth more than an elegant estate that only works while the person who built it is still answering their phone.

Common questions

Answered, directly.

What buyers ask about delivering this in nonprofit.

One per object per timing is the right default, not a rule. Split deliberately where two pieces of logic have different owners or different release cadences, because forcing separate teams into one artefact turns a design problem into a coordination problem. What is not defensible is several flows nobody intended, each added because the existing one looked too risky to open. Whatever the number, it should be one somebody chose and can defend.

It is deliberately not a rewrite. The logic was mostly correct and the team could not afford to re-prove it. The work is an audit, then a naming pass, then consolidation on the two busiest objects only, then fault paths. Each stage ships on its own and leaves the org better than it found it, which matters when the same two people are also handling the ticket queue.

Put the convention in the build template and the definition of done rather than in a review step, because reviews get skipped under deadline and nobody goes back to rename. The second control is ownership recorded as a role rather than a person, so a flow does not become an orphan on the day somebody leaves. Neither needs a budget.

Free architect conversation

Talk to an architect, not a sales rep.

60 seconds to brief us, and a certified architect replies within one business day.

What are you looking to architect?

Pick the closest fit. You can add detail in a moment.

Which clouds or systems are in scope?

Optional. Choose any that apply, or skip ahead.

Where does your org stand today?

Optional. A few sentences is plenty: what is working, what is stuck, and what you want to be true. Or skip ahead and tell us on the call.

Who should the architect reach?

A certified architect will reply to these details.

Takes about 30–60 seconds · No obligation · Architect replies within one business day

Protected by reCAPTCHA. Google's Privacy Policy and Terms apply.

The thinking behind this

Related reading

Where this sits

Salesforce Administrator Services

This work is delivered through our salesforce administrator services practice.

Talk to us about running your org

More delivery

Related case studies

  • SynconAI Consulting are Simply the Best in the Business When it comes to Salesforce implementation and AI-powered solutions, SynconAI are in a league of their own. Their expertise, dedication, and ability to deliver results that truly move the needle sets them apart from every other consulting partner we've worked with. What they achieved with our Sales Cloud, Service Cloud, and custom Agentforce agent has completely transformed how we operate streamlining our pipelines and our customer service, and automating tasks that used to consume hours of our team's time. They don't just implement technology they transform businesses. If you want the best, you work with their team.

    Jack BennettConsumer Goods & RetailUnited States

  • I had a very urgent deadline for our project reporting to be delivered and needed to build the module, dashboard and output reports in Salesforce. SynconAI were very responsive - they met with me online, responded to my emails quickly and took calls - whatever was needed to progress the work quickly. We are thrilled with the outcome and will continue to work with SynconAI in the future to continue to build our Salesforce capability and platform.

    Salesforce Managed ServicesAustralia

  • The team at SynconAI were fantastic, and promptly delivered on each project. They were able to guide us through every stage and made sure we were satisfied with the outcomes on multiple scopes of work.

    ManufacturingAustralia

  • They have been great and helping us transform out business by bringing what I conceptualize to life. Great at translating process/workflow needs into a solution. Been a pleasure work with and they're a critical part of our team and will be instrumental into bringing about my vision.

    Financial ServicesUnited States

  • Working with SynconAI was a seamless and highly professional experience from start to finish. They took the time to deeply understand our business processes before designing and implementing a Salesforce solution tailored specifically to our operational needs. The team demonstrated strong technical expertise across Salesforce configuration, automation, integrations, reporting, and user experience design.

    Salesforce Implementation