# WhatsApp Nudges — Comprehensive Inventory

**Last updated:** 2026-06-09
**Scope:** Every direct-fire WhatsApp + in-app nudge built into the multi-admin recruiter (`kaabil-recruiter-multi-admin`) codebase. Covers what's shipped, what's tested, schedule lines for crontab, trigger logic, every parameter rendered into the message body, and the actual message text users receive.

This is the **single reference** for product / ops / QA to understand which nudge does what — separate from the strategy doc which tracks intent and progress.

---

## At-a-glance summary

| # | Nudge | Audience | Schedule | Status | Azmarq template |
|---|---|---|---|---|---|
| R7 | Job Expiring 3 Days | Recruiter | Daily 9 AM IST | ✅ live on UAT | `cron_job_expiry_alert_recruiter_3_days` |
| R5 | Interview Reminder — 1 Day | Recruiter | Daily 9 AM IST | ✅ live on UAT, ⚠️ template pending Azmarq creation | `cron_interview_reminder_recruiter_1_day` |
| R6 | Interview Reminder — 10 Min | Recruiter | Every minute | ✅ live on UAT, ⚠️ template pending Azmarq creation | `cron_interview_reminder_recruiter_10_min` |
| J3 | Interview Reminder — 1 Day | Jobseeker | Daily 9 AM IST | ✅ live on UAT, ⚠️ template pending Azmarq creation | `cron_interview_reminder_jobseeker_1_day` |
| J4 | Interview Reminder — 10 Min | Jobseeker | Every minute | ✅ live on UAT | `llcron_job_seekers_interview_reminder_10_minutes_before_lt` |
| R8 | Weekly Status Update | Recruiter | Monday 10 AM IST | 🔨 built, dry-run passed | `cron_recruiter_status_update_weekly` |
| J5 | Weekly Job Alerts | Jobseeker | Monday 9 AM IST | 🔨 built, dry-run passed | `weekly_job_alerts` |
| R4 | No Job Posted Reminder | Recruiter | Daily 9 AM IST | 🔨 built, dry-run passed | `no_job_posting_reminder` |

**⚠️ template pending Azmarq creation** = The cron + code is shipped and will fire `wa_nudge_log` rows with status `failed` for the WhatsApp channel until the named template is created and approved in Azmarq. The in-app channel works independently.

---

## Cron schedule lines for crontab (PROD)

Add these to `crontab -e` on the PROD recruiter EC2 (`tools.kaabil.cc`). All times are 24h UTC (cron). IST is UTC+5:30.

```cron
# R7 — Job Expiring 3 Days     | daily 9 AM IST = 3:30 AM UTC
30 3 * * * /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "R7-Job-Expiring-Soon-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/job-expiring-soon.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log

# R5 — Recruiter Interview Reminder 1 Day  | daily 9 AM IST = 3:30 AM UTC
30 3 * * * /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "R5-Recruiter-Interview-1Day-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/recruiter-interview-reminder-1day.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log

# R6 — Recruiter Interview Reminder 10 Min | every minute
* * * * * /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "R6-Recruiter-Interview-10Min-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/recruiter-interview-reminder-10min.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log

# J3 — Jobseeker Interview Reminder 1 Day  | daily 9 AM IST = 3:30 AM UTC
30 3 * * * /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "J3-Jobseeker-Interview-1Day-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/jobseeker-interview-reminder-1day.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log

# J4 — Jobseeker Interview Reminder 10 Min | every minute
* * * * * /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "J4-Jobseeker-Interview-10Min-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/jobseeker-interview-reminder-10min.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log

# R8 — Recruiter Weekly Status   | Monday 10 AM IST = 4:30 AM UTC every Monday
30 4 * * 1 /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "R8-Recruiter-Status-Weekly-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/recruiter-status-weekly.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log

# J5 — Jobseeker Weekly Job Alerts | Monday 9 AM IST = 3:30 AM UTC every Monday
30 3 * * 1 /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "J5-Jobseeker-Weekly-Job-Alerts-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/jobseeker-weekly-job-alerts.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log

# R4 — No Job Posted Reminder    | daily 9 AM IST = 3:30 AM UTC
30 3 * * * /var/www/tools.kaabil.cc/recruiter/cron/cron-with-alert.sh "R4-Recruiter-No-Job-Posted-PROD" "php /var/www/tools.kaabil.cc/recruiter/cron/recruiter-no-job-posted.php" /var/www/tools.kaabil.cc/recruiter/logs/cron.log
```

For UAT, replace `tools.kaabil.cc` with `uat.kaabil.cc` and `-PROD` with `-UAT` in the alert names.

---

## R7 — Job Expiring 3 Days

**File:** [`app/Services/JobExpiry3DayNudgeService.php`](../app/Services/JobExpiry3DayNudgeService.php) · [`cron/job-expiring-soon.php`](../cron/job-expiring-soon.php)

### What it does
Sends a recruiter a WhatsApp + in-app red-dot when one of their job postings will expire in exactly 3 days. The goal is to prompt them to "Repost" (which extends visibility) before the listing falls off.

### Schedule
- **Cron:** Daily at 9 AM IST (`30 3 * * *` UTC)
- **Window:** `DATE(company_jobs.job_expiry) = DATE_ADD(CURDATE(), INTERVAL 3 DAY)`

### Trigger conditions (all must be true)
- Job is **Active** (`company_jobs.status = 2`)
- Job's `job_expiry` is exactly 3 days from today (date-granular, not hour-granular)
- The job's `created_by` recruiter has a non-empty `users.mobile`

### Idempotency
- One WhatsApp ever per `(job_id)` — once nudged, never re-nudged for that specific job. Entity in `wa_nudge_log` is `('job', job_id)`.

### Body parameters (Azmarq template order)
| Placeholder | Source | Example |
|---|---|---|
| `{{1}}` first_name | `users.name` → first word | `Priya` |
| `{{2}}` job_title | `company_jobs.job_title` | `Sales Executive` |
| `{{3}}` repost_url | Built from host: `tools.kaabil.cc/recruiter/repost_job.php?id={job_id}` | `https://tools.kaabil.cc/recruiter/repost_job.php?id=1234` |

### The WhatsApp message
```
Hi Priya,

Your job post for Sales Executive will expire in 3 days.

🔄 Repost now to keep attracting top female talent.
💬 Also, share your feedback on candidate quality to help us improve your hiring experience!

👉 Repost Job: https://tools.kaabil.cc/recruiter/repost_job.php?id=1234

Thank you,
Team Kaabil
```

### In-app notification text
> Your job 'Sales Executive' expires in 3 day(s). Repost to keep it visible.

`routsId = 3` (Job details). Red dot on recruiter dashboard.

### nudge_key
`r7_job_expiry_3day`

---

## R5 — Recruiter Interview Reminder, 1 Day

**File:** [`app/Services/InterviewReminderNudgeService.php`](../app/Services/InterviewReminderNudgeService.php) · [`cron/recruiter-interview-reminder-1day.php`](../cron/recruiter-interview-reminder-1day.php)

### What it does
Reminds a recruiter that they have a scheduled interview happening tomorrow. Avoids no-shows on the interviewer side.

### Schedule
- **Cron:** Daily at 9 AM IST (`30 3 * * *` UTC)
- **Window:** `DATE(users_job.schedule_call_date) = DATE_ADD(CURDATE(), INTERVAL 1 DAY)`

### Trigger conditions (all must be true)
- Interview is scheduled (`users_job.schedule_status = 1`)
- Application is in schedule-pending OR confirmed (`users_job.job_status IN (3, 4)`)
- `users_job.schedule_slot_master_id` is set (joined to `schedule_slot_master` for the time)
- `schedule_call_date` is **exactly tomorrow** (date-granular)
- Recruiter has non-empty `users.mobile`

### Idempotency
- One WhatsApp per `(users_job.id)` per cron run. Entity is `('users_job', users_job.id)`. Re-runs the same day will skip.

### Body parameters
| Placeholder | Source | Example |
|---|---|---|
| `{{1}}` first_name | recruiter's `users.name` short | `Priya` |
| `{{2}}` candidate_name | jobseeker's `users.name` short | `Rajesh` |
| `{{3}}` job_role | `company_jobs.job_title` (fall back to `profession_master.name`) | `Sales Executive` |
| `{{4}}` interview_time | `CONCAT(date, ' ', schedule_slot_master.start_time)` formatted | `10 Jun 2026, 11:00 AM` |
| `{{5}}` company_name | `company_master.name` | `Capgemini` |

### The WhatsApp message *(target — pending Azmarq template approval)*
```
Hi Priya,

Reminder: You have an interview scheduled with Rajesh for Sales Executive at Capgemini tomorrow at 11:00 AM.

Please be ready and login on time. 👉 https://tools.kaabil.cc/recruiter/

Team Kaabil
```

### In-app notification text
> Reminder: Interview with Rajesh for Sales Executive is tomorrow.

`routsId = 3`. Red dot on recruiter dashboard.

### nudge_key
`r5_recruiter_interview_1day`

---

## R6 — Recruiter Interview Reminder, 10 Min Before

**File:** [`app/Services/InterviewReminderNudgeService.php`](../app/Services/InterviewReminderNudgeService.php) · [`cron/recruiter-interview-reminder-10min.php`](../cron/recruiter-interview-reminder-10min.php)

### What it does
Last-mile reminder ~10 minutes before the interview's actual start time. Critical for keeping recruiter availability rate high.

### Schedule
- **Cron:** Every minute (`* * * * *`)
- **Window:** `CONCAT(schedule_call_date, ' ', schedule_slot_master.start_time) BETWEEN NOW() + 9 MIN AND NOW() + 12 MIN`
- The 9-12 min window (3-min envelope) tolerates cron-tick drift so we don't miss the slot.

### Trigger conditions
Same as R5 plus the tighter time-window above.

### Idempotency
- One WhatsApp per `(users_job.id)` per nudge_key. Entity is `('users_job', users_job.id)`.

### Body parameters
| Placeholder | Source | Example |
|---|---|---|
| `{{1}}` first_name | recruiter's name | `Priya` |
| `{{2}}` candidate_name | jobseeker's name | `Rajesh` |

### The WhatsApp message *(target — pending Azmarq template approval)*
```
Hi Priya,

Final reminder: Your interview with Rajesh starts in 10 minutes. Good luck!

Team Kaabil
```

### In-app notification text
> Interview with Rajesh starts in 10 minutes.

### nudge_key
`r6_recruiter_interview_10min`

---

## J3 — Jobseeker Interview Reminder, 1 Day

**File:** [`app/Services/InterviewReminderNudgeService.php`](../app/Services/InterviewReminderNudgeService.php) · [`cron/jobseeker-interview-reminder-1day.php`](../cron/jobseeker-interview-reminder-1day.php)

### What it does
Reminds the jobseeker their interview is tomorrow. Same trigger as R5 but the recipient is the candidate.

### Schedule + Conditions
Identical to R5 — same SQL query, same filters. The only difference is which recipient's mobile is used.

### Body parameters
| Placeholder | Source | Example |
|---|---|---|
| `{{1}}` first_name | jobseeker's `users.name` short | `Rajesh` |
| `{{2}}` company_name | `company_master.name` | `Capgemini` |
| `{{3}}` job_role | `company_jobs.job_title` | `Sales Executive` |
| `{{4}}` interview_time | `CONCAT(date, ' ', start_time)` formatted | `10 Jun 2026, 11:00 AM` |

### The WhatsApp message *(target — pending Azmarq template approval)*
```
Hi Rajesh,

Reminder: Your interview with Capgemini for Sales Executive is scheduled tomorrow at 11:00 AM.

Please be prepared and login on time. 👉 https://kaabilprogram.org/

Best of luck! 
Team Kaabil
```

### In-app notification text
> Reminder: Interview with Capgemini for Sales Executive is tomorrow.

`routsId = 3`. Red dot on jobseeker app.

### nudge_key
`j3_jobseeker_interview_1day`

---

## J4 — Jobseeker Interview Reminder, 10 Min Before

**File:** [`app/Services/InterviewReminderNudgeService.php`](../app/Services/InterviewReminderNudgeService.php) · [`cron/jobseeker-interview-reminder-10min.php`](../cron/jobseeker-interview-reminder-10min.php)

### What it does
Last-mile reminder to the jobseeker, mirror of R6.

### Schedule + Conditions
Identical to R6.

### Body parameters
| Placeholder | Source | Example |
|---|---|---|
| `{{1}}` company_name | `company_master.name` | `Capgemini` |
| `{{2}}` job_role | `company_jobs.job_title` | `Sales Executive` |

### The WhatsApp message *(Azmarq approved template — Hindi/Hinglish)*
```
*Interview Reminder*

Final reminder: Aapka interview Capgemini ke saath Sales Executive ke liye 10 minute mein shuru ho raha hai. Good luck!
```

### In-app notification text
> Your interview with Capgemini for Sales Executive starts in 10 minutes.

### nudge_key
`j4_jobseeker_interview_10min`

### Azmarq template name
`llcron_job_seekers_interview_reminder_10_minutes_before_lt` (the `ll` prefix denotes "lifestage / language" tag in Cogniv's naming convention)

---

## R8 — Recruiter Weekly Status Update

**File:** [`app/Services/RecruiterStatusWeeklyNudgeService.php`](../app/Services/RecruiterStatusWeeklyNudgeService.php) · [`cron/recruiter-status-weekly.php`](../cron/recruiter-status-weekly.php)

### What it does
Once a week, prompts recruiters who shortlisted candidates in the past 7 days to come back and update statuses (selected / rejected / hired). Reduces stalled pipelines.

### Schedule
- **Cron:** Every Monday at 10 AM IST (`30 4 * * 1` UTC)

### Trigger conditions (all must be true)
- Recruiter shortlisted ≥ 1 candidate (`users_job.job_status = 2`) in the past 7 days
- Recruiter has non-empty `users.mobile`
- Recruiter has not been nudged in the past 144 hours (6 days) — `LOOKBACK_HOURS = 144`

### What the SQL picks
For each recruiter, picks **ONE job** to feature in the message — the job with the **most shortlists this week** (tiebreaker: most-recent shortlist). Window function:
```sql
ROW_NUMBER() OVER (
  PARTITION BY cj.created_by
  ORDER BY COUNT(uj.id) DESC, MAX(uj.created_date) DESC
) AS rn
WHERE rn = 1
```

### Idempotency
- One WhatsApp per recruiter per 6-day rolling window. Entity is `('recruiter', recruiter_user_id)`.

### Body parameters
| Placeholder | Source | Example |
|---|---|---|
| `{{1}}` first_name | `users.name` short | `Priya` |
| `{{2}}` shortlisted_count | count of `users_job.job_status=2` in last 7d for the featured job | `7` |
| `{{3}}` job_role | `COALESCE(cj.job_title, pm.name)` | `Sales Executive` |
| `{{4}}` company_name | `cm.name` | `Capgemini` |

### The WhatsApp message *(Azmarq approved)*
```
Hi Priya,

You have shortlisted 7 candidates for the job role Sales Executive at Capgemini this week.

Please review and update the status for each candidate in your job post:

✅ If selected, mark as Selected
❌ If not selected, mark as Rejected

👉 Login & Update Status: https://kaabilprogram.org/

Keeping statuses updated helps us maintain accurate records and improve the hiring process.

Thank you for your support!
Team Kaabil
```

### In-app notification text
> Weekly status: you shortlisted 7 candidates for Sales Executive this week. Review and update.

`routsId = 3`.

### nudge_key
`r8_recruiter_status_weekly`

### Azmarq template name
`cron_recruiter_status_update_weekly` (Utility category)

---

## J5 — Jobseeker Weekly Job Alerts

**File:** [`app/Services/JobseekerWeeklyJobAlertsNudgeService.php`](../app/Services/JobseekerWeeklyJobAlertsNudgeService.php) · [`cron/jobseeker-weekly-job-alerts.php`](../cron/jobseeker-weekly-job-alerts.php)

### What it does
Re-engagement blast for active jobseekers who have at least one matching job available. Lowest-effort retention nudge.

### Schedule
- **Cron:** Every Monday at 9 AM IST (`30 3 * * 1` UTC)

### Trigger conditions (all must be true)
- User is active (`users.status = 1`)
- User is **not** a recruiter — `LEFT JOIN user_companies uc WHERE uc.id IS NULL`
- User has non-empty `users.mobile`
- User has **at least one row in `company_jobs_automatch` matched to an active job** (`INNER JOIN company_jobs cj ON cj.status = 2`)
- User has not been nudged in past 144 hours

### Idempotency
- One per jobseeker per 6-day rolling window. Entity is `('jobseeker', user_id)`.

### Body parameters
**None.** Template body is fully static — no `{{n}}` placeholders. We send empty `components` array.

### The WhatsApp message *(Azmarq approved, Hindi/Hinglish)*
```
Job alert!
Is hafte ke liye naye job matches ready hain. Jaldi kaabilprogram.org par login karein aur apply karke apna chance  login karein aur apply kijiye!
```

*(Note: the template body has a duplicated "login karein aur apply" phrase — likely an Azmarq-side copy-paste. Worth cleanup in the Azmarq template editor.)*

### In-app notification
**None.** Pure WhatsApp re-engagement — by definition the user isn't in the app right now.

### nudge_key
`j5_jobseeker_weekly_job_alerts`

### Azmarq template name
`weekly_job_alerts` (Marketing category)

---

## R4 — Recruiter No Job Posted Reminder

**File:** [`app/Services/RecruiterNoJobPostedNudgeService.php`](../app/Services/RecruiterNoJobPostedNudgeService.php) · [`cron/recruiter-no-job-posted.php`](../cron/recruiter-no-job-posted.php)

### What it does
Nudges recruiters who signed up, completed onboarding, but never posted a job. Reduces signup-without-activation leakage.

### Schedule
- **Cron:** Daily at 9 AM IST (`30 3 * * *` UTC)

### Trigger conditions (all must be true)
- User is a recruiter — has `user_companies.role_type IN ('admin', 'co_admin', 'sub_admin')` with `status = 1`
- Account is active (`users.status = 1`)
- User signed up **≥ 7 days ago** (`users.created_date <= DATE_SUB(NOW(), INTERVAL 7 DAY)`)
- User has **never posted a job** — `LEFT JOIN company_jobs cj ON cj.created_by = u.id WHERE cj.id IS NULL`
- User has non-empty `users.mobile`
- User has **never been nudged** by R4 — lookback is `null` (any send ever counts)

### Idempotency
- **One WhatsApp ever** per recruiter — once nudged, never re-nudged even if they delete their first job later. Entity is `('recruiter', recruiter_user_id)`, lookback is `null`.

### Body parameters
| Placeholder | Source | Example |
|---|---|---|
| `{{1}}` first_name | `users.name` short | `Rajesh` |

### The WhatsApp message *(Azmarq approved)*
```
Hi Rajesh,
you haven't posted any jobs yet. Start posting now to discover the talent you need!
```

### In-app notification text
> You haven't posted any jobs yet. Tap to post your first job and start hiring.

`routsId = 1` (Home / dashboard).

### nudge_key
`r4_recruiter_no_job_posted`

### Azmarq template name
`no_job_posting_reminder` (Marketing category)

---

## Shared infrastructure

### `wa_nudge_log` (audit + idempotency table)

Single table for every direct-fire WhatsApp + in-app nudge. Created by R7's migration `migrations/011_wa_nudge_log.sql`.

Columns:
- `id`, `nudge_key`, `entity_type`, `entity_id`, `recipient_user_id`, `recipient_mobile`, `channel` (`whatsapp` / `in_app`), `template_name`, `status` (`sent`, `failed`, `dry_run`, `short_circuited`), `payload_json`, `error_text`, `sent_at`

Indexes:
- `idx_nudge_entity_channel` (`nudge_key`, `entity_type`, `entity_id`, `channel`) — for idempotency lookup
- `idx_recipient_user` (`recipient_user_id`)
- `idx_sent_at` (`sent_at`)

### `WhatsappNudgeService` (abstract base class)
Each concrete nudge service extends this. Provides:
- `alreadyNudgedWhatsApp(entityType, entityId, lookbackHours)` — checks for prior send in window
- `sendWhatsAppNudge(...)` — fires Azmarq via `NotificationService::sendWhatsApp()`, writes audit row
- `writeInAppNudge(...)` — inserts into `users_notification`, writes audit row
- `log(...)` — single source of truth for `wa_nudge_log` inserts
- `shortFirstName($name)` — "Priya Sharma" → "Priya", null/empty → "there"
- `buildRecruiterUrl($path)` — host-aware (tools.kaabil.cc vs uat.kaabil.cc vs localhost)

### `NotificationService` (the actual sender)
Wraps Azmarq API + Firebase + in-app. The `sendWhatsApp($mobile, $templateName, $components)` method is where the rubber meets the road. Local-dev short-circuit built in (so cloned data doesn't accidentally fire real messages).

WA template constants currently defined (all in `app/Services/NotificationService.php`):
```php
const WA_TEMPLATE_SHORTLISTED                = 'on_trigger_shortlisted_by_recruiter';
const WA_TEMPLATE_SELECTED                   = 'on_trigger_hired_by_recruiter';
const WA_TEMPLATE_INTERVIEW                  = 'on_trigger_interview_scheduled';

// R7 + interview reminder family (added 2026-06-08)
const WA_TEMPLATE_JOB_EXPIRY_3DAY            = 'cron_job_expiry_alert_recruiter_3_days';
const WA_TEMPLATE_RECRUITER_INTERVIEW_1DAY   = 'cron_interview_reminder_recruiter_1_day';
const WA_TEMPLATE_RECRUITER_INTERVIEW_10MIN  = 'cron_interview_reminder_recruiter_10_min';
const WA_TEMPLATE_JOBSEEKER_INTERVIEW_1DAY   = 'cron_interview_reminder_jobseeker_1_day';
const WA_TEMPLATE_JOBSEEKER_INTERVIEW_10MIN  = 'llcron_job_seekers_interview_reminder_10_minutes_before_lt';

// Batch 2 (added 2026-06-09)
const WA_TEMPLATE_RECRUITER_STATUS_WEEKLY    = 'cron_recruiter_status_update_weekly';
const WA_TEMPLATE_JOBSEEKER_JOB_ALERTS_WEEKLY = 'weekly_job_alerts';
const WA_TEMPLATE_RECRUITER_NO_JOB_POSTED    = 'no_job_posting_reminder';
const WA_TEMPLATE_JOBSEEKER_PROFILE_PCT      = 'jobseeker_profile_completion_percentage';
```

---

## Testing each nudge — universal recipe

For any of the crons above, the same recipe applies:

```bash
# 1. Dry-run (writes wa_nudge_log rows with status='dry_run', no Azmarq calls)
php cron/<cron-name>.php --test

# 2. Live run
php cron/<cron-name>.php

# 3. Re-run immediately to test idempotency (should show skipped_already > 0)
php cron/<cron-name>.php

# 4. Verify audit
mysql -h <db host> -u <user> -p <db> -e "
  SELECT id, nudge_key, channel, status, recipient_mobile, sent_at
  FROM wa_nudge_log
  WHERE nudge_key = 'r8_recruiter_status_weekly'
  ORDER BY id DESC LIMIT 10;
"
```

---

## Open items and what's pending

| Item | Notes |
|---|---|
| Create R5/R6/J3 templates in Azmarq | Crons currently fire but `failed_whatsapp` instead of sending. In-app still works. |
| Build J1 (jobseeker 24h/48h/72h after OTP, 3-stage) | Needs per-user stage-tracking state. Separate MR. |
| Build J2 (profile %, daily, max 5 sends per user) | Needs parity with `LoginModel::getJobseekerProfilePercentage` for the % calculation. |
| Build R1 (recruiter onboarding incomplete) | Define what "complete" means first (`is_profile_verify=1`? plus phone verified? plus first job posted?) |
| Build R2 (profile verification pending) | Triggered by document-upload state — needs to surface verification table state. |
| Build R3 (recruiter incomplete profile) | Similar to R1 — define "complete". |
| Build the leakage funnels | `registered_but_not_applied_day1/3/5`, `inactive_after_application_48h/72h/day5`, `recruiter_no_action_post_application_72h/day5`, `shortlisted_followup_48h/72h` — each is multi-stage, needs separate state design. |
| Daily Digest 7pm recruiter | Check first if existing CleverTap journey handles this. |
| Test R5/R6/J3/J4/R7 fire end-to-end with synthetic UAT rows | All have dry-runs passing but no real-fire confirmation yet (UAT has no near-future interviews). |
| Schedule the crons in UAT crontab | The code is deployed but the cron lines aren't yet added to UAT crontab. Add them and watch for one cycle. |
