Skip to content

MSP Contact Lifecycle

Last updated: 2026-10-01.

This page documents the implemented CRM Contact automations that keep MSP Seats aligned with onboarding, recurring, and offboarding contact states. Each seat is owned by an Account and linked to a Contact. The eligible Service authorises MSP enrolment but is not the seat's parent.

Contact Created

When a Contact is created, the automation tries to create or reuse an MSP Seat for the Contact if the Contact belongs to a company with exactly one active Service with MSP Seats Enabled checked.

Rules currently implemented:

  • If the Contact has no company, no seat is created.
  • If the Contact came from the onboarding form and has no company, a warning email is sent to the configured debug recipients so the Contact can be fixed.
  • Eligibility requires the same Company, Service Status Active, and MSP_Seats_Enabled saved as true. Blank or false does not enrol contacts.
  • If the company has no eligible Service, no seat is created.
  • If the company has more than one eligible Service, the automation stops and sends a manual-review email. It does not guess which plan is correct.
  • If the Contact or active MSP plan is missing an ID, the automation stops for manual review.
  • If more than one MSP Seat already exists for the same Contact and Account, the automation stops for manual review.
  • If exactly one matching MSP Seat already exists, the automation reuses it and still ensures the Contact lead status is ONBOARDING.
  • If no matching MSP Seat exists, the automation creates one with:
    • Account: the Contact company, matching the eligible Service's Account;
    • Contact: the created Contact;
    • Billing_Status: Onboarding;
    • Billing_Start: the Contact Created_Time date;
    • Billing_End: blank.
  • Whenever the automation can safely continue past the plan/seat checks, it ensures the Contact Lead_Status is ONBOARDING.

The retained Service module uses the Client_Services API. Its Plan_Status field and MSP_Seats_Enabled flag retain the enrolment gate. Project and other Services can be active without enabling MSP contact automation. New seats have Account and Contact lookups; they do not write the legacy Seat Plan field.

Business reasoning:

  • A new staff Contact for an MSP client should become billable/onboarding data only when the owning company and MSP entitlement are unambiguous.
  • Onboarding starts as a seat lifecycle state, but the Contact also moves to ONBOARDING so existing CRM views and team workflows remain aligned.
  • Duplicate Account/Contact seats and multiple eligible Services require review, preserving the existing enrolment gate. A seat missing its Account also requires review before contact or seat changes. Automation leaves their resolution to a person.

Contact Edited

When a Contact is edited, the automation looks at update_source first, then at Lead_Status.

Offboarding Form

If update_source is offboarding_form, the automation treats the edit as an offboarding event.

Rules currently implemented:

  • The same company / single active plan / single matching seat checks apply.
  • If a corresponding MSP Seat exists, it is ended:
    • Billing_Status: Ended;
    • Billing_End: today.
  • If the seat is already Ended and already has a Billing_End, the existing end date is preserved.
  • After the seat is ended or confirmed ended, the Contact is updated to:
    • Lead_Status: BUSINESS PREVIOUS;
    • update_source: blank.

Recurring Contact Status

If Lead_Status is BUSINESS RECURRING or BUSINESS OWNER RECURRING, the Contact should have an active MSP Seat.

Rules currently implemented:

  • If no corresponding MSP Seat exists, one is created with Billing_Status Active and Billing_Start set to today.
  • If a corresponding seat exists but is ended or onboarding, it is updated to:
    • Billing_Status: Active;
    • Billing_End: blank.
  • If the seat is already active with no end date, no seat change is needed.

Onboarding Contact Status

If Lead_Status is ONBOARDING, the Contact should have an onboarding MSP Seat.

Rules currently implemented:

  • If no corresponding MSP Seat exists, one is created with Billing_Status Onboarding and Billing_Start set to today.
  • If a corresponding seat exists, it is updated to Billing_Status Onboarding.
  • Billing_End is cleared when needed so the seat can participate in the current lifecycle again.

Business Previous Status

If Lead_Status is BUSINESS PREVIOUS, the Contact should stop counting as an active MSP seat only when the current seat is active.

Rules currently implemented:

  • If a corresponding seat is Active, it is ended with Billing_Status Ended and Billing_End set to today.
  • If the corresponding seat is not active, the automation skips rather than forcing a different lifecycle transition.
  • If no corresponding seat exists, the automation skips.

Other Edits

If the edit is not an offboarding form event and the Contact lead status is not one of the implemented MSP lifecycle statuses, no MSP Seat change is made.

Review and Skip Rules

The automation skips safely when:

  • the Contact has no company;
  • the company has no active MSP plan;
  • the edit does not match an implemented MSP lifecycle case;
  • a BUSINESS PREVIOUS Contact has no active corresponding seat to end.

The automation asks for manual review when:

  • the company has multiple active MSP plans;
  • the Contact or plan is missing an ID;
  • more than one MSP Seat exists for the same Contact and Account;
  • an existing seat that must be updated has no seat ID.

Manual-review and warning emails are sent to DEBUG_RECIPIENT_EMAILS through Zoho Mail.

Deliberate Limits

  • The Deluge functions do not contain business logic; they only pass the Contact ID to middleware.
  • The automation does not choose between multiple active plans.
  • The automation does not merge duplicate seats.
  • The automation does not create or update Client Services.

References

  • Technical route: POST /zoho/contacts/on-create
  • Technical route: POST /zoho/contacts/on-edit
  • Code: app/automations/contact_created.py
  • Code: app/automations/contact_edited.py
  • Deluge wrappers: zoho_crm/deluge/standalone.on_create_contact_automations.dg, zoho_crm/deluge/standalone.on_edit_contact_automations.dg
  • Tests: tests/test_contact_created_automation.py, tests/test_contact_edited_automation.py