PushMail.dev

Lead Scoring

Automatically score contacts based on email engagement, tag activity, and custom events. Use scores to prioritize outreach, segment audiences, and trigger automations.

Overview

Lead scoring assigns numeric points to contacts based on their behavior — email opens, link clicks, tag assignments, and custom events. Each contact has a leadScore field that accumulates points over time as scoring rules fire.

Use lead scores to:

  • Identify your most engaged contacts
  • Segment audiences by engagement level (e.g. "hot leads" with score > 50)
  • Trigger sequences when contacts reach a score threshold
  • Deprioritize inactive contacts with negative scoring

How it works

  1. Create scoring rules that map events to point values
  2. When an event occurs (email opened, link clicked, tag added, etc.), the scoring engine checks all active rules
  3. If a rule matches, points are added to the contact's leadScore and a log entry is recorded
  4. You can also manually adjust scores via the API

The scoring rule object

{
  "id": 1,
  "orgId": 1,
  "siteId": null,
  "name": "Email opened",
  "eventType": "email_open",
  "eventFilter": null,
  "points": 5,
  "maxPerContact": 10,
  "active": true,
  "createdAt": "2026-03-01T10:00:00.000Z",
  "updatedAt": "2026-03-01T10:00:00.000Z"
}

Event types

Event typeFires whenExample use
email_openContact opens an email+5 points per open
email_clickContact clicks a link in an email+10 points per click
email_deliveredEmail is successfully delivered+1 point per delivery
tag_addedA tag is applied to the contact+20 for "demo-requested" tag
custom_eventYour app sends a custom event+15 for "completed_onboarding"
manualScore adjusted via API+/- any amount

Event filters

Use eventFilter to narrow when a rule fires. The filter is a JSON object where each key must match the event data.

Only score clicks on pricing page
{
  "eventType": "email_click",
  "eventFilter": { "url": "https://pushmail.dev/pricing" }
}
Only score when 'enterprise' tag is added
{
  "eventType": "tag_added",
  "eventFilter": { "tagName": "enterprise" }
}
Match multiple URLs
{
  "eventType": "email_click",
  "eventFilter": { "url": ["https://pushmail.dev/pricing", "https://pushmail.dev/demo"] }
}

Rate limiting with maxPerContact

Set maxPerContact to cap how many times a rule can fire for a single contact. This prevents inflated scores from repeated opens or clicks.

  • maxPerContact: 10 — rule stops firing after 10 times for each contact
  • maxPerContact: null — no limit (rule fires every time)

Create a scoring rule

POST /v1/scoring/rules

ParameterTypeDescription
namerequiredstringHuman-readable rule name (max 200 chars)
eventTyperequiredstringOne of: email_open, email_click, email_delivered, tag_added, custom_event, manual
pointsrequiredintegerPoints to award (can be negative for penalties)
siteIdintegerScope rule to a specific site. Omit for org-wide.
eventFilterobjectJSON filter to narrow event matching
maxPerContactintegerMax times this rule fires per contact
activebooleanWhether the rule is active (default: true)
Request
curl -X POST https://pushmail.dev/api/v1/scoring/rules \
  -H "Authorization: Bearer pm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Email opened",
    "eventType": "email_open",
    "points": 5,
    "maxPerContact": 10
  }'
Response
{
  "data": {
    "id": 1,
    "orgId": 1,
    "siteId": null,
    "name": "Email opened",
    "eventType": "email_open",
    "eventFilter": null,
    "points": 5,
    "maxPerContact": 10,
    "active": true,
    "createdAt": "2026-03-01T10:00:00.000Z"
  }
}

List scoring rules

GET /v1/scoring/rules

ParameterTypeDescription
siteIdintegerFilter rules by site (query param). Omit to list all org rules.
Request
curl "https://pushmail.dev/api/v1/scoring/rules" \
  -H "Authorization: Bearer pm_live_YOUR_KEY"

Get a scoring rule

GET /v1/scoring/rules/:id

Request
curl https://pushmail.dev/api/v1/scoring/rules/1 \
  -H "Authorization: Bearer pm_live_YOUR_KEY"

Update a scoring rule

PUT /v1/scoring/rules/:id

ParameterTypeDescription
namestringNew rule name
eventTypestringNew event type
eventFilterobject|nullNew filter (set to null to clear)
pointsintegerNew point value
maxPerContactinteger|nullNew limit (set to null for unlimited)
activebooleanEnable or disable the rule
Request
curl -X PUT https://pushmail.dev/api/v1/scoring/rules/1 \
  -H "Authorization: Bearer pm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "points": 10,
    "active": false
  }'

Delete a scoring rule

DELETE /v1/scoring/rules/:id

Existing scoring logs for this rule are preserved (the ruleId reference becomes null).

Request
curl -X DELETE https://pushmail.dev/api/v1/scoring/rules/1 \
  -H "Authorization: Bearer pm_live_YOUR_KEY"

Get a contact's score

GET /v1/contacts/:id/score

Returns the contact's current lead score and recent scoring log entries.

ParameterTypeDescription
limitintegerNumber of log entries to return, 1-100 (default: 20)
Request
curl https://pushmail.dev/api/v1/contacts/42/score \
  -H "Authorization: Bearer pm_live_YOUR_KEY"
Response
{
  "data": {
    "contactId": 42,
    "email": "jane@example.com",
    "leadScore": 45,
    "logs": [
      {
        "id": 10,
        "ruleId": 1,
        "points": 5,
        "reason": "Rule: Email opened",
        "createdAt": "2026-03-01T15:00:00.000Z",
        "ruleName": "Email opened"
      },
      {
        "id": 9,
        "ruleId": 2,
        "points": 10,
        "reason": "Rule: Link clicked",
        "createdAt": "2026-03-01T14:30:00.000Z",
        "ruleName": "Link clicked"
      },
      {
        "id": 8,
        "ruleId": null,
        "points": 25,
        "reason": "Sales team qualification",
        "createdAt": "2026-03-01T12:00:00.000Z",
        "ruleName": null
      }
    ]
  }
}

Manually adjust a contact's score

POST /v1/contacts/:id/score

Add or subtract points from a contact's score with an optional reason. Useful for sales team adjustments, support escalations, or integrations.

ParameterTypeDescription
pointsrequiredintegerPoints to add (positive) or subtract (negative)
reasonstringWhy this adjustment was made (max 500 chars)
Request — add points
curl -X POST https://pushmail.dev/api/v1/contacts/42/score \
  -H "Authorization: Bearer pm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "points": 25,
    "reason": "Attended product demo"
  }'
Response
{
  "data": {
    "contactId": 42,
    "pointsAdded": 25,
    "newScore": 70
  }
}
Request — subtract points
curl -X POST https://pushmail.dev/api/v1/contacts/42/score \
  -H "Authorization: Bearer pm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "points": -10,
    "reason": "Unresponsive for 30 days"
  }'

Example: typical scoring ruleset

Here is a common set of rules for a B2B SaaS product:

RuleEvent typePointsMax per contact
Email deliveredemail_delivered+150
Email openedemail_open+510
Link clickedemail_click+1020
Clicked pricing pageemail_click (filter: pricing URL)+153
Tagged as "demo-requested"tag_added (filter: tagName)+301
Completed onboardingcustom_event (filter: name)+251
Spam complaintemail_delivered (negative)-501

Using scores in segments

Combine lead scores with segments to create dynamic audiences based on engagement level:

Create a high-value segment
curl -X POST https://pushmail.dev/api/v1/segments \
  -H "Authorization: Bearer pm_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "siteId": 1,
    "name": "Hot Leads",
    "rules": {
      "operator": "AND",
      "conditions": [
        { "type": "attribute", "field": "status", "op": "eq", "value": "subscribed" },
        { "type": "attribute", "field": "lead_score", "op": "gte", "value": 50 }
      ]
    }
  }'

Next steps

  • Contacts -- Manage contact records and view lead scores
  • Segments -- Build dynamic audiences using lead score thresholds
  • Webhooks -- Receive events that trigger scoring rules

On this page