Skip to main content

Lead Nurture

Verify a lead's email with an Elixir hook, score it through an HTTP hook, branch on the result, then alert sales or push to nurture and update CRM.

Sequence steps

Follow each step as it queues up, sends, and completes.

Sequence code
tenant_key = "demo"

{:ok, sequence} =
  DripDrop.create_sequence(%{
    tenant_key: tenant_key,
    name: "Lead nurture demo",
    key: "lead-nurture",
    description:
      "Lead qualification sequence for exercising Elixir hooks, HTTP hooks, predicates, and CRM webhooks.",
    hook_module: "Elixir.DripdropDemo.LeadNurtureHooks",
    active: true,
    metadata: %{"demo" => true}
  })

{:ok, lead_score_hook} =
  DripDrop.create_http_hook(sequence.id, %{
    tenant_key: tenant_key,
    name: "Lead score API",
    key: "lead_score",
    description: "Scores a fixture lead from the demo HTTP hook server.",
    method: "GET",
    url: DripdropDemo.MockHooks.url("/lead-score?lead_id={{ lead_id }}"),
    response_type: "number",
    response_path: "score",
    timeout_ms: 2_000,
    retry_count: 0
  })

{:ok, version} =
  DripDrop.create_sequence_version(sequence.id, %{
    version: 1,
    name: "Hook and predicate branches",
    mode: :lifecycle,
    config: %{"demo_time_scale" => Application.fetch_env!(:dripdrop_demo, :demo_time_scale)}
  })

{:ok, slack_adapter} =
  DripDrop.create_channel_adapter(%{
    tenant_key: tenant_key,
    name: "Mock Slack",
    channel: "slack",
    provider: "webhook",
    credentials: %{"url" => DripdropDemo.MockHooks.url("/slack-alert")},
    active: true
  })

{:ok, branch_anchor} =
  DripDrop.create_step(version.id, %{
    name: "Branch decision anchor",
    key: "branch-anchor",
    position: 1,
    channel: "pubsub",
    channel_adapter_id: pubsub_adapter.id,
    timing: %{type: "immediate"},
    template_type: "inline",
    template_content: %{
      "topic" => "demo:lead_nurture",
      "event" => "branch.anchor",
      "payload" => %{
        "title" => "Consulting lead",
        "message" => "{{ first_name }} at {{ company }} asked about {{ interest }}."
      }
    }
  })

{:ok, nurture_email} =
  DripDrop.create_step(version.id, %{
    name: "Nurture email",
    key: "nurture-email",
    position: 2,
    channel: "email",
    channel_adapter_id: email_adapter.id,
    timing: %{type: "delay", delay_amount: 8, delay_unit: "seconds"},
    template_type: "inline",
    template_content: %{
      "subject" => "A few Elixir notes for {{ company }}",
      "text" =>
        "Hi {{ first_name }}, here are a few practical ways to keep Phoenix systems healthy.",
      "html" =>
        "<p>Hi {{ first_name }}, here are a few practical ways to keep Phoenix systems healthy.</p>"
    }
  })

{:ok, slack_notification} =
  DripDrop.create_step(version.id, %{
    name: "Slack notification",
    key: "slack-notification",
    position: 3,
    channel: "slack",
    channel_adapter_id: slack_adapter.id,
    timing: %{type: "delay", delay_amount: 6, delay_unit: "seconds"},
    template_type: "inline",
    template_content: %{
      "channel" => "#sales",
      "text" =>
        "New high-fit consulting lead: {{ company }} is ready for Elixir/Phoenix follow-up."
    }
  })

{:ok, crm_hot} =
  DripDrop.create_step(version.id, %{
    name: "CRM hot-lead update",
    key: "crm-hot-lead",
    position: 4,
    channel: "webhook",
    channel_adapter_id: webhook_adapter.id,
    timing: %{type: "delay", delay_amount: 6, delay_unit: "seconds"},
    template_type: "inline",
    template_content: %{
      "url" => DripdropDemo.MockHooks.url("/crm-update"),
      "method" => "post",
      "body" => %{
        "type" => "crm.lead_qualified",
        "stage" => "sales_ready",
        "source" => "dripdrop_demo",
        "contact" => %{
          "name" => "{{ first_name }}",
          "email" => "{{ email }}",
          "phone" => "{{ sms }}",
          "company" => "{{ company }}"
        },
        "qualification" => %{
          "interest" => "{{ interest }}",
          "budget" => "{{ budget }}",
          "company_size" => "{{ company_size }}"
        }
      }
    }
  })

{:ok, crm_nurture} =
  DripDrop.create_step(version.id, %{
    name: "CRM nurture update",
    key: "crm-nurture",
    position: 5,
    channel: "webhook",
    channel_adapter_id: webhook_adapter.id,
    timing: %{type: "delay", delay_amount: 6, delay_unit: "seconds"},
    template_type: "inline",
    template_content: %{
      "url" => DripdropDemo.MockHooks.url("/crm-update"),
      "method" => "post",
      "body" => %{
        "type" => "crm.lead_nurture",
        "stage" => "nurture",
        "source" => "dripdrop_demo",
        "contact" => %{
          "name" => "{{ first_name }}",
          "email" => "{{ email }}",
          "company" => "{{ company }}"
        },
        "qualification" => %{
          "interest" => "{{ interest }}",
          "budget" => "{{ budget }}",
          "company_size" => "{{ company_size }}"
        }
      }
    }
  })

# Decision 1: direct Elixir module hook.
#
# DripDrop calls DripdropDemo.LeadNurtureHooks.handle_hook/3.
# That host hook can call the GoodVerify.dev Elixir client directly:
#
# client = GoodverifyEx.client(api_key: System.fetch_env!("GOODVERIFY_API_KEY"))
# {:ok, result} = GoodverifyEx.verify_email(client, %{email: enrollment.data["email"]})
# result.deliverability.status == "deliverable"

{:ok, invalid_transition} =
  DripDrop.create_step_transition(version.id, %{
    from_step_id: branch_anchor.id,
    to_step_id: nil,
    condition_mode: "all",
    priority: 1
  })

DripDrop.create_condition(invalid_transition.id, %{
  condition_type: "hook",
  hook_function: "verify_email",
  operator: "==",
  expected_value: "false"
})

{:ok, high_fit_transition} =
  DripDrop.create_step_transition(version.id, %{
    from_step_id: branch_anchor.id,
    to_step_id: slack_notification.id,
    condition_mode: "all",
    priority: 2
  })

DripDrop.create_condition(high_fit_transition.id, %{
  condition_type: "hook",
  hook_function: "verify_email",
  operator: "==",
  expected_value: "true"
})

# Decision 2: external HTTP hook.
#
# DripDrop calls the configured lead_score_hook and compares
# response.score against the condition below.

DripDrop.create_condition(high_fit_transition.id, %{
  condition_type: "hook",
  http_hook_id: lead_score_hook.id,
  operator: ">=",
  expected_value: "70"
})

DripDrop.create_condition(high_fit_transition.id, %{
  condition_type: "predicate",
  operator: "==",
  config: %{"predicate" => "enrollment.company_size >= 50"}
})

{:ok, nurture_transition} =
  DripDrop.create_step_transition(version.id, %{
    from_step_id: branch_anchor.id,
    to_step_id: nurture_email.id,
    condition_mode: "all",
    priority: 3
  })

DripDrop.create_condition(nurture_transition.id, %{
  condition_type: "hook",
  hook_function: "verify_email",
  operator: "==",
  expected_value: "true"
})

DripDrop.create_step_transition(version.id, %{
  from_step_id: slack_notification.id,
  to_step_id: crm_hot.id,
  condition_mode: "always",
  priority: 1
})

DripDrop.create_step_transition(version.id, %{
  from_step_id: nurture_email.id,
  to_step_id: crm_nurture.id,
  condition_mode: "always",
  priority: 1
})

for terminal_step <- [crm_hot, crm_nurture] do
  DripDrop.create_step_transition(version.id, %{
    from_step_id: terminal_step.id,
    to_step_id: nil,
    condition_mode: "always",
    priority: 1
  })
end

{:ok, _validated} = DripDrop.validate_sequence_version(version.id)
{:ok, _activated} = DripDrop.activate_sequence_version(version.id)

Sequence messages

Messages appear here as each branch step is sent.

Sequence logs
# Live DripDrop events will stream here after dispatch starts.
A project by Goodway — we build software that drives results.