packages/twenty-docs/user-guide/workflows/how-tos/crm-automations/detect-stale-opportunities.mdx
Keep your pipeline healthy by alerting managers when opportunities go stale. This workflow checks for opportunities that haven't been updated in a specified number of days.
Opportunities sitting without updates lead to:
Create a scheduled workflow that finds stale opportunities and emails their managers.
| Field | Value |
|---|---|
| Object | Opportunities |
| Filter | Updated At is before (today - 7 days) |
| Filter | Stage is not "Closed Won" AND not "Closed Lost" |
| Limit | 100 |
{{searchRecords.length}} is greater than 0Add a Code action to format the email:
export const main = async (params) => {
const opportunities = params.opportunities;
// Group opportunities by owner
const byOwner = {};
opportunities.forEach(opp => {
const ownerEmail = opp.owner?.email || 'unassigned';
if (!byOwner[ownerEmail]) {
byOwner[ownerEmail] = [];
}
byOwner[ownerEmail].push({
name: opp.name,
amount: opp.amount,
lastUpdated: opp.updatedAt,
stage: opp.stage
});
});
// Format summary for manager
let summary = "Stale Opportunities Report\n\n";
Object.entries(byOwner).forEach(([owner, opps]) => {
summary += `${owner}: ${opps.length} stale opportunities\n`;
opps.forEach(opp => {
summary += ` - ${opp.name} (${opp.stage})\n`;
});
summary += "\n";
});
return {
summary,
totalCount: opportunities.length
};
};
Add Send Email action:
| Field | Value |
|---|---|
| To | [email protected] |
| Subject | 🚨 {{code.totalCount}} Stale Opportunities Need Attention |
| Body | {{code.summary}} |
Modify the Search Records filter to change from 7 days to your preferred period:
Instead of one manager email, use Iterator to send personalized emails to each rep about their own stale deals.
Create multiple workflows with increasing severity:
Use HTTP Request to post to a Slack webhook instead of or in addition to email.