import MermaidChart from '../../../components/MermaidChart';
Valstorm was built by a Software Engineer who specialized in developing the processes and tools for Sales Operations teams. On this page, we'll break down what we've learned about Sales Operations over the years and how you can apply those principles to your team, regardless of whether you're using Valstorm or competing tools.
A Customer Relationship Management (CRM) system is the backbone of any Sales Operations team. It stores all your customer data, tracks interactions, and helps manage your sales pipeline. Popular CRMs include Salesforce, HubSpot, and Zoho CRM. Valstorm also developed its own CRM, ValRM.
We won't focus on a specific CRM here, but rather on the general principles that apply to any CRM you choose. Regardless of your industry, these are the principles to follow.
At its core, a CRM should help you manage your relationships with customers and prospects. This includes:
The truth is, while Salesforce and HubSpot can do all this, they often require heavy configuration. When you first purchase a major CRM, it can feel like a glorified spreadsheet that needs multiple apps and skilled consultants to become truly useful. HubSpot is easier to use out of the box but is often limited to marketing, while sales and service teams gravitate toward Salesforce.
Valstorm started as a consulting company. We've been there and done that. We've built custom CRMs for clients in various industries and have seen the pitfalls of improper configuration.
Following the plan laid out in this document will empower your team to spend far more time moving deals along and working with customers.
CRMs are built around the concept of objects and records.
Understanding that an object is just the best way to represent a category of data is crucial. As your team and departments grow, you will need to use more of them.
A lead is a person or company you can contact about a potential business operation. While leads can be used in marketing and service for partnerships or referrals, we will focus on the sales cycle.
A Lead has two outcomes: Qualified or Disqualified. If disqualified, they are no longer pursued. If a lead isn't ready now but might be in the future (e.g., in two years when your average deal cycle is one), they should be put On Hold and re-engaged later.
Once a lead is qualified, it converts into an opportunity, which is a potential sale tracked through your pipeline until it is either Won (resulting in revenue) or Lost.
A common pitfall is treating leads as contacts. These objects should be separate entities in your CRM to properly track a prospect's journey.
This structure allows you to track a person's entire history with your business, even across different jobs or multiple purchase cycles.
<MermaidChart chart={` graph TD subgraph "Data Relationships" L(Lead) --> C(Contact) L --> CO(Company) C --> CO end
style L fill:#e3f2fd,stroke:#b3e5fc
style C fill:#e8f5e9,stroke:#c8e6c9
style CO fill:#fbe9e7,stroke:#ffccbc
`} />
This process should be automated to prevent duplicate records, which can wreak havoc on your communication systems and metrics.
This can be simplified by ensuring Contacts are unique.
With this system, communications like calls, texts, and emails can all link to the right person and company, ensuring your metrics are accurate and your automation is reliable.
Tasks are actionable steps. Many teams hate them because their completion isn't automated, turning them into ignored reminders. When set up correctly, the team should be able to follow tasks without much thought, performing the right action at the right time.
The goal is to ensure that all tasks, whether related to a lead, contact, or opportunity, are completed the day they are due. A systematic approach to tasks keeps your pipeline moving forward.
Moving leads through a sales pipeline involves a few key stages that apply to almost any industry.
<MermaidChart chart={` graph LR A[New Lead] --> B(Attempted Contact) B --> C{Engaged} C --> D((Nurture)) C --> E[/Disqualified/] C --> F((Qualified))
style E fill:#ffebee,stroke:#ef9a9a
style F fill:#e8f5e9,stroke:#a5d6a7
`} />
For more detail, use a Stage Reason field. A lead might be in the Nurture stage for different reasons like Budget, Timing, or Researching. Use these reasons to further classify and automate your lead follow-up.
Opportunities are the next step after a lead is qualified. This is where the real sales process begins. An opportunity is the brain of the sale and should be linked to other objects like quotes, proposals, and contracts.
Convert a Lead once you identify that:
While lead stages are fairly standard, opportunity pipelines can vary widely. Here are some general principles:
Let's actually talk about some specific automations you should implement in your CRM to streamline sales operations. This will be broken down by object type since that majorly changes the process.
Assume we have the following lead statuses:
Automating the transitions between these statuses based on real-world interactions is key to maintaining a clean pipeline and ensuring timely follow-ups.
<MermaidChart chart={` sequenceDiagram participant User/System participant Lead
User/System->>Lead: Create Lead (Status: New) User/System->>Lead: Log Call/Email (Status is New) Lead-->>Lead: Update Status to "Attempted Contact"
User/System->>Lead: Log Response (Status is Attempted Contact) Lead-->>Lead: Update Status to "Engaged"
alt Not ready to buy User/System->>Lead: Update field (e.g., "Nurture Reason") Lead-->>Lead: Update Status to "Nurture" else Not a fit User/System->>Lead: Update field (e.g., "Disqualification Reason") Lead-->>Lead: Update Status to "Disqualified" else Ready to buy User/System->>Lead: Book Meeting or Request Proposal Lead-->>Lead: Update Status to "Qualified" end `} />
Here are some Valstorm record trigger examples to automate these status changes.
This After Create trigger on a Communication object (like a Call or Email) checks if the related lead is "New." If so, it updates the lead's status.
# Trigger on: Communication Object - After Create from valstorm.dependencies import add_log, valstorm_client def execute(new_data: list, current_user, **kwargs): try: lead_id = new_data[0].get('lead_id') if not lead_id: return new_data # Fetch the related lead record lead = valstorm_client.get_record('leads', lead_id) # If the lead's status is 'New', update it if lead and lead.get('status') == 'New': valstorm_client.update_record('leads', lead_id, {'status': 'Attempted Contact'}) add_log(f"Lead {lead_id} status updated to Attempted Contact.", 'info') except Exception as e: add_log(f"Error in New to Attempted Contact trigger: {e}", 'error') return new_data
This After Update trigger on a Communication object fires when a response is logged. It then updates the lead's status to "Engaged."
# Trigger on: Communication Object - After Update from valstorm.dependencies import add_log, valstorm_client def execute(new_data: list, old_data: list, current_user, **kwargs): try: # Check if the 'responded' flag was just changed to True if new_data[0].get('responded') and not old_data[0].get('responded'): lead_id = new_data[0].get('lead_id') if lead_id: valstorm_client.update_record('leads', lead_id, {'status': 'Engaged'}) add_log(f"Lead {lead_id} status updated to Engaged.", 'info') except Exception as e: add_log(f"Error in Attempted Contact to Engaged trigger: {e}", 'error') return new_data
This After Update trigger on the Lead object itself manages the next steps. It checks for changes in specific fields to determine the correct new status.
# Trigger on: Lead Object - After Update from valstorm.dependencies import add_log, valstorm_client def execute(new_data: list, old_data: list, current_user, **kwargs): lead = new_data[0] previous_lead = old_data[0] lead_id = lead.get('id') try: # Only proceed if the lead was 'Engaged' if previous_lead.get('status') != 'Engaged': return new_data new_status = None # Logic to move to Nurture if lead.get('nurture_reason') and not previous_lead.get('nurture_reason'): new_status = 'Nurture' # Logic to move to Disqualified elif lead.get('disqualification_reason') and not previous_lead.get('disqualification_reason'): new_status = 'Disqualified' # Logic to move to Qualified (e.g., a meeting is booked) elif lead.get('meeting_booked_date') and not previous_lead.get('meeting_booked_date'): new_status = 'Qualified' if new_status: valstorm_client.update_record('leads', lead_id, {'status': new_status}) add_log(f"Lead {lead_id} status updated to {new_status}.", 'info') except Exception as e: add_log(f"Error in Engaged status change trigger: {e}", 'error') return new_data