Every Monday, each rep gets one Slack message listing their Salesforce deals with no activity or stage change in two weeks, biggest first.
UsesSalesforce
Slack
Nudge reps about stalled Salesforce deals
Deals rarely die in a meeting. They die quietly, when nobody has called, emailed or moved them for weeks, and the sales manager finds out at the quarterly review. Every Monday at 08:00, Salesforce is asked for every open opportunity with no logged activity and no stage change in the last 14 days (a number you can change), and each rep gets one Slack direct message listing theirs, biggest first: the account, amount, stage, close date, how many days it has been idle and a link straight to the deal. A rep with nothing stalled hears nothing. If you name a sales manager, they get one summary with each rep's count and total.
Close dates that have already passed are called out. Deals still owned by someone no longer active in Salesforce are sent to nobody, and the manager's summary names them so they can be reassigned. A rep Slack cannot find by their Salesforce email is named in the summary and the run is marked as failed, while everyone else still gets their list. If Salesforce cannot be read, nobody is messaged and the run fails saying why, so an outage never looks like a quiet week. Running it again the same day sends nobody a second copy.
You need Salesforce and Slack connected, and your Salesforce address (such as https://acme.my.salesforce.com) pasted into the workflow. The sales manager's Slack email is optional.
salesforceAddress to blank.Set salesforceAddress to blank.daysIdle to 14.Set daysIdle to 14.managerEmail to blank.Set managerEmail to blank.currency to USD.Set currency to USD.maxDealsPerMessage to 20.Set maxDealsPerMessage to 20.currency and save it as currency.Trim currency and save it as currency.currency uppercase and save it as currency.Make currency uppercase and save it as currency.currency is not 3If the length of currency is not 3managerEmail and save it as managerEmail.Trim managerEmail and save it as managerEmail.managerEmail isn't blank and managerEmail doesn't contain @If managerEmail isn't blank and managerEmail doesn't contain @salesforceAddress and save it as salesforceLink.Trim salesforceAddress and save it as salesforceLink.salesforceLink doesn't contain ://If salesforceLink doesn't contain ://salesforceLink to https://@salesforceLink.Change salesforceLink to https://@salesforceLink.salesforceLink and save it as salesforceHost.Extract the first domain from salesforceLink and save it as salesforceHost..lightning.force.com with .my.salesforce.com in salesforceHost and save it as salesforceHost.Replace .lightning.force.com with .my.salesforce.com in salesforceHost and save it as salesforceHost..my.salesforce-setup.com with .my.salesforce.com in salesforceHost and save it as salesforceHost.Replace .my.salesforce-setup.com with .my.salesforce.com in salesforceHost and save it as salesforceHost.salesforceHost doesn't end with .my.salesforce.comIf salesforceHost doesn't end with .my.salesforce.comtoday.Get today's date and save it as today.daysBack to daysIdle multiplied by -1.Set daysBack to daysIdle multiplied by -1.today by daysBack days and save it as cutoffDate.Move today by daysBack days and save it as cutoffDate.cutoffDate and save it as dayAfterCutoff.Get the date 1 day after cutoffDate and save it as dayAfterCutoff.dayAfterCutoff readable as “2026-11-02T14:05:00Z” and save it as stageCutoff.Make dayAfterCutoff readable as “2026-11-02T14:05:00Z” and save it as stageCutoff.stalledDeals to an empty list.Set stalledDeals to an empty list.afterId to blank.Set afterId to blank.morePages to true.Set morePages to true.pagesRead to 0.Set pagesRead to 0.readCapped to false.Set readCapped to false.morePages is yes:While morePages is yes:idFilter to blank.Set idFilter to blank.afterId isn't blankIf afterId isn't blankAND Id > '@afterId' and save it as idFilter.Build the text AND Id > '@afterId' and save it as idFilter.salesforceErrorIf a step below fails, save the error as salesforceErrorstalledPage.Run a query. Save it as stalledPage.SELECT Id, Name, Amount, StageName, CloseDate, LastActivityDate, LastStageChangeDate, CreatedDate, Owner.Name, Owner.Email, Owner.IsActive, Account.Name FROM Opportunity WHERE IsClosed = false AND (LastActivityDate = null OR LastActivityDate <= cutoffDate) AND (LastStageChangeD...stalledDeals and stalledPage.records into one list and save it as stalledDeals.Combine stalledDeals and stalledPage.records into one list and save it as stalledDeals.morePages to false.Change morePages to false.stalledPage.records isn't emptyIf stalledPage.records isn't emptystalledPage.done is no or the length of stalledPage.records is greater than or…If stalledPage.done is no or the length of stalledPage.records is greater than or…afterId to stalledPage.records[-1].Id.Change afterId to stalledPage.records[-1].Id.morePages to true.Change morePages to true.Could not read your open opportunities from Salesforce, so nobody was messaged. Salesforce said: , salesforceError.message with the pattern ([^.!?])$ replaced by $1., Check the Salesforce connection in Lodol, then run the workflow again.] joined by nothing.Stop and report a problem: [Could not read your open opportunities from Salesforce, so nobody was messaged. Salesforce said: , salesforceError.message with the pattern ([^.!?])$ replaced by $1., Check the Salesforce connection in Lodol, then run the workflow again.] joined by nothing.pagesRead.Add 1 to pagesRead.morePages is yes and pagesRead is greater than or equal to 10If morePages is yes and pagesRead is greater than or equal to 10readCapped to true.Change readCapped to true.morePages to false.Change morePages to false.stalledDeals and save it as stalledCount.Count the items in stalledDeals and save it as stalledCount.stalledCount is 0If stalledCount is 0daysIdle days or more without activity or a stage change, so nobody was messaged.Finish the run: No open opportunity has gone daysIdle days or more without activity or a stage change, so nobody was messaged.alreadySent to an empty list.Set alreadySent to an empty list.logErrorIf a step below fails, save the error as logErrornudgeLog.Get the stored value 'stalled_deal_nudges'. Save it as nudgeLog.nudgeLog.found is yesIf nudgeLog.found is yesnudgeLog.value where item starts with today and save it as alreadySent.Keep each item in nudgeLog.value where item starts with today and save it as alreadySent.Amount from every item in stalledDeals and save it as dealAmounts.Get Amount from every item in stalledDeals and save it as dealAmounts.dealAmounts and save it as stalledValue.Add up dealAmounts and save it as stalledValue.stalledValue as a currency with 0 decimal places and save it as stalledValueText.Format stalledValue as a currency with 0 decimal places and save it as stalledValueText., worth @stalledValueText and save it as summaryValueNote.Build the text , worth @stalledValueText and save it as summaryValueNote.stalledValue is 0If stalledValue is 0summaryValueNote to blank.Change summaryValueNote to blank.stalledDeals by Owner.Email, with the total Amount of each and save it as repGroups.Group the items in stalledDeals by Owner.Email, with the total Amount of each and save it as repGroups.repGroups by total, largest first and save it as repsByValue.Sort repGroups by total, largest first and save it as repsByValue.repsMessaged to an empty list.Set repsMessaged to an empty list.repsAlreadySent to 0.Set repsAlreadySent to 0.unreachedReps to an empty list.Set unreachedReps to an empty list.inactiveOwners to an empty list.Set inactiveOwners to an empty list.unrecorded to an empty list.Set unrecorded to an empty list.managerLines to an empty list.Set managerLines to an empty list.rep in repsByValue:For each rep in repsByValue:rep.items by Amount, largest first and save it as repDeals.Sort rep.items by Amount, largest first and save it as repDeals.repName to repDeals[0].Owner.Name.Set repName to repDeals[0].Owner.Name.rep.key lowercase and save it as repEmail.Make rep.key lowercase and save it as repEmail.@today @repEmail and save it as sentKey.Build the text @today @repEmail and save it as sentKey.rep.total as a currency with 0 decimal places and save it as repValue.Format rep.total as a currency with 0 decimal places and save it as repValue.dealWord to deals.Set dealWord to deals.verbWord to have.Set verbWord to have.rep.count is 1If rep.count is 1dealWord to deal.Change dealWord to deal.verbWord to has.Change verbWord to has.repNote to blank.Set repNote to blank.repEmail is blankIf repEmail is blankrepName to Owner with no email address in Salesforce.Change repName to Owner with no email address in Salesforce.repNote to (nobody to message).Change repNote to (nobody to message).rep.count dealWord whose owner has no email address Salesforce would return to the end of unreachedReps and save it as unreachedReps.Add rep.count dealWord whose owner has no email address Salesforce would return to the end of unreachedReps and save it as unreachedReps.repNote to (no longer active in Salesforce, so nobody was messaged).Change repNote to (no longer active in Salesforce, so nobody was messaged).repName to the end of inactiveOwners and save it as inactiveOwners.Add repName to the end of inactiveOwners and save it as inactiveOwners.repsAlreadySent.Add 1 to repsAlreadySent.dealLines to an empty list.Set dealLines to an empty list.maxDealsPerMessage items of repDeals and save it as listedDeals.Take the first maxDealsPerMessage items of repDeals and save it as listedDeals.deal in listedDeals:For each deal in listedDeals:deal.LastStageChangeDate, or deal.CreatedDate if it is empty and save it as stageMoved. Use deal.LastStageChangeDate, or deal.CreatedDate if it is empty and save it as stageMoved.stageMoved readable as “2026-11-02” and save it as lastTouched. Make stageMoved readable as “2026-11-02” and save it as lastTouched.deal.LastActivityDate is greater than lastTouched If deal.LastActivityDate is greater than lastTouchedlastTouched to deal.LastActivityDate.Change lastTouched to deal.LastActivityDate.lastTouched to today and save it as idleFor. Count the days from lastTouched to today and save it as idleFor.dealAmount to no amount. Set dealAmount to no amount.deal.Amount isn't empty If deal.Amount isn't emptydeal.Amount as a currency with 0 decimal places and save it as dealAmount.Format deal.Amount as a currency with 0 decimal places and save it as dealAmount.deal.CloseDate readable as “Nov 2, 2026” and save it as closeDay. Make deal.CloseDate readable as “Nov 2, 2026” and save it as closeDay.closeNote to closes closeDay. Set closeNote to closes closeDay.deal.CloseDate is less than today If deal.CloseDate is less than todayclose date @closeDay has passed and save it as closeNote.Build the text close date @closeDay has passed and save it as closeNote.deal.Account.Name, or no account if it is empty and save it as accountName. Use deal.Account.Name, or no account if it is empty and save it as accountName.< with < in deal.Name and save it as dealName. Replace < with < in deal.Name and save it as dealName.> with > in dealName and save it as dealName. Replace > with > in dealName and save it as dealName.accountName · dealAmount · deal.StageName · closeNote · idleFor days idle to the end of dealLines and save it as dealLines. Add • <https://@salesforceHost/@deal.Id|@dealName> · accountName · dealAmount · deal.StageName · closeNote · idleFor days idle to the end of dealLines and save it as dealLines.dealLines with a new line and save it as dealList.Join dealLines with a new line and save it as dealList.moreNote to blank.Set moreNote to blank.rep.count is greater than maxDealsPerMessageIf rep.count is greater than maxDealsPerMessageunlistedCount to rep.count minus maxDealsPerMessage.Set unlistedCount to rep.count minus maxDealsPerMessage._Plus @unlistedCount more not shown here, smaller or without an amount._ and save it as moreNote.Build the text _Plus @unlistedCount more not shown here, smaller or without an amount._ and save it as moreNote., worth @repValue and save it as valueNote.Build the text , worth @repValue and save it as valueNote.rep.total is 0If rep.total is 0valueNote to blank.Change valueNote to blank.slackErrorIf a step below fails, save the error as slackErrorrepEmail. Save it as repDm.Send a direct message to repEmail. Save it as repDm.*rep.count of your deals verbWord gone quiet@valueNote*
No logged activity and no stage change in Salesforce for daysIdle days or more. Biggest first:
dealList@moreNote
Log a call, email or meeting on a deal, or move it to its next stage, and it drops off this list. If a d...repName to the end of repsMessaged and save it as repsMessaged.Add repName to the end of repsMessaged and save it as repsMessaged.sentKey to the end of alreadySent and save it as alreadySent.Add sentKey to the end of alreadySent and save it as alreadySent.recordErrorIf a step below fails, save the error as recordErroralreadySent. Save it as sentRecorded.Set the stored value 'stalled_deal_nudges' to alreadySent. Save it as sentRecorded.repName to the end of unrecorded and save it as unrecorded.Add repName to the end of unrecorded and save it as unrecorded.repNote to (could not be messaged in Slack).Change repNote to (could not be messaged in Slack).repName (repEmail): slackError.message to the end of unreachedReps and save it as unreachedReps.Add repName (repEmail): slackError.message to the end of unreachedReps and save it as unreachedReps.repName: rep.count dealWord, repValue@repNote to the end of managerLines and save it as managerLines.Add • repName: rep.count dealWord, repValue@repNote to the end of managerLines and save it as managerLines.summaryTo to blank.Set summaryTo to blank.summaryProblem to blank.Set summaryProblem to blank.managerEmail isn't blankIf managerEmail isn't blank@today summary to @managerEmail and save it as summaryKey.Build the text @today summary to @managerEmail and save it as summaryKey.alreadySent doesn't contain summaryKeyIf alreadySent doesn't contain summaryKeymanagerLines with a new line and save it as repList.Join managerLines with a new line and save it as repList.summaryNotes to an empty list.Set summaryNotes to an empty list.unreachedReps isn't emptyIf unreachedReps isn't emptyunreachedReps with a new line and save it as unreachedList.Join unreachedReps with a new line and save it as unreachedList.unreachedList to the end of summaryNotes and save it as summaryNotes.inactiveOwners isn't emptyIf inactiveOwners isn't emptyinactiveOwners with , and save it as inactiveList.Join inactiveOwners with , and save it as inactiveList.inactiveList. Reassign these deals so someone picks them up. to the end of summaryNotes and save it as summaryNotes.Add *Still owned by people no longer active in Salesforce:* inactiveList. Reassign these deals so someone picks them up. to the end of summaryNotes and save it as summaryNotes.readCapped is yesIf readCapped is yesSalesforce had more than 20,000 stalled deals and only the first 20,000 were read, so these figures are not complete. to the end of summaryNotes and save it as summaryNotes.Add Salesforce had more than 20,000 stalled deals and only the first 20,000 were read, so these figures are not complete. to the end of summaryNotes and save it as summaryNotes.summaryNotes with a blank line and save it as notesText.Join summaryNotes with a blank line and save it as notesText.summaryErrorIf a step below fails, save the error as summaryErrormanagerEmail. Save it as summaryDm.Send a direct message to managerEmail. Save it as summaryDm.*Deals gone quiet: stalledCount@summaryValueNote*
Open opportunities with no logged activity and no stage change in Salesforce for daysIdle days or more. Each owner below has been sent their own list in Slack unless it says otherwise. By owner, biggest first:
repList
notes...summaryTo to managerEmail.Change summaryTo to managerEmail.summaryKey to the end of alreadySent and save it as alreadySent.Add summaryKey to the end of alreadySent and save it as alreadySent.summaryRecordErrorIf a step below fails, save the error as summaryRecordErroralreadySent. Save it as sentRecorded.Set the stored value 'stalled_deal_nudges' to alreadySent. Save it as sentRecorded.the manager's summary to the end of unrecorded and save it as unrecorded.Add the manager's summary to the end of unrecorded and save it as unrecorded.The summary could not be sent to @managerEmail: @summaryError.message and save it as summaryProblem.Build the text The summary could not be sent to @managerEmail: @summaryError.message and save it as summaryProblem.runNotes to an empty list.Set runNotes to an empty list.repsMessaged and save it as messagedCount.Count the items in repsMessaged and save it as messagedCount.messagedCount is greater than 0If messagedCount is greater than 0repsMessaged with , and save it as messagedList.Join repsMessaged with , and save it as messagedList.messagedCount rep(s) their list: messagedList. to the end of runNotes and save it as runNotes.Add Sent messagedCount rep(s) their list: messagedList. to the end of runNotes and save it as runNotes.repsAlreadySent is greater than 0If repsAlreadySent is greater than 0repsAlreadySent rep(s) had already been sent today's list, so it was not sent again. to the end of runNotes and save it as runNotes.Add repsAlreadySent rep(s) had already been sent today's list, so it was not sent again. to the end of runNotes and save it as runNotes.summaryTo isn't blankIf summaryTo isn't blanksummaryTo. to the end of runNotes and save it as runNotes.Add Sent the summary to summaryTo. to the end of runNotes and save it as runNotes.inactiveOwners isn't emptyIf inactiveOwners isn't emptyinactiveOwners with , and save it as inactiveNames.Join inactiveOwners with , and save it as inactiveNames.inactiveNames. Reassign their deals. to the end of runNotes and save it as runNotes.Add Not messaged because they are no longer active in Salesforce: inactiveNames. Reassign their deals. to the end of runNotes and save it as runNotes.problems to an empty list.Set problems to an empty list.unreachedReps isn't emptyIf unreachedReps isn't emptyunreachedReps with ; and save it as unreachedText.Join unreachedReps with ; and save it as unreachedText.unreachedText. Check that their Slack email matches the one in Salesforce. to the end of problems and save it as problems.Add Could not message in Slack: unreachedText. Check that their Slack email matches the one in Salesforce. to the end of problems and save it as problems.summaryProblem isn't blankIf summaryProblem isn't blanksummaryProblem. to the end of problems and save it as problems.Add summaryProblem. to the end of problems and save it as problems.unrecorded isn't emptyIf unrecorded isn't emptyunrecorded with , and save it as unrecordedText.Join unrecorded with , and save it as unrecordedText.unrecordedText. to the end of problems and save it as problems.Add Sent, but could not note it as sent, so running this again today would send it a second time: unrecordedText. to the end of problems and save it as problems.readCapped is yesIf readCapped is yesSalesforce had more than 20,000 stalled deals; only the first 20,000 were read, so some reps may be missing deals. Raise 'daysIdle' to narrow the list. to the end of problems and save it as problems.Add Salesforce had more than 20,000 stalled deals; only the first 20,000 were read, so some reps may be missing deals. Raise 'daysIdle' to narrow the list. to the end of problems and save it as problems.Found @stalledCount open deal(s) worth @stalledValueText with no activity or stage change for @daysIdle days or more. and save it as runSummary.Build the text Found @stalledCount open deal(s) worth @stalledValueText with no activity or stage change for @daysIdle days or more. and save it as runSummary.runNotes with a space and save it as notesLine.Join runNotes with a space and save it as notesLine.problems isn't emptyIf problems isn't emptyproblems with a space and save it as problemsLine.Join problems with a space and save it as problemsLine.runSummary notesLine problemsLine with the pattern (?<!\.)\.\.(?!\.) replaced by . with the pattern {2,} replaced by .Stop and report a problem: runSummary notesLine problemsLine with the pattern (?<!\.)\.\.(?!\.) replaced by . with the pattern {2,} replaced by .runSummary notesLine with the pattern (?<!\.)\.\.(?!\.) replaced by . with the pattern {2,} replaced by .Finish the run: runSummary notesLine with the pattern (?<!\.)\.\.(?!\.) replaced by . with the pattern {2,} replaced by .