# IAP Status Notifications Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add automated candidate-facing email notifications for IAP status changes (New, Processing, Eligible, Ineligible) and centralize all notification logic including the existing Leadership Review flow into a shared helper.

**Architecture:** Create `iap_notifications.php` as a single helper containing all email templates and the `send_iap_notification()` function. Replace the broken SendGrid block in `submit_application.php` with a call to the helper. Replace the Leadership Review SendGrid block in `sync_application_status.php` and add calls for the three new statuses.

**Tech Stack:** PHP 8, SendGrid PHP SDK (`\SendGrid\Mail\Mail`), `envv()` for env vars, procedural style matching the rest of the IAP module.

---

## File Map

| Action | File | Purpose |
|---|---|---|
| Create | `app/HR/IAP/iap_notifications.php` | All IAP email templates + `send_iap_notification()` |
| Modify | `app/HR/IAP/submit_application.php` | Replace broken SendGrid block (lines 114–137) with helper call |
| Modify | `app/HR/IAP/sync_application_status.php` | Replace Leadership Review block (lines 43–85), add Processing/Eligible/Ineligible notifications |

---

## Task 1: Create iap_notifications.php

**Files:**
- Create: `app/HR/IAP/iap_notifications.php`

- [ ] **Step 1: Create the file**

```php
<?php
// app/HR/IAP/iap_notifications.php
declare(strict_types=1);

define('IAP_HR_EMAIL',         'hr@whitewatercw.com');
define('IAP_RECRUITING_EMAIL', 'recruiting@whitewatercw.com');
define('IAP_FROM_EMAIL',       'noreply@whitewatercw.com');
define('IAP_FROM_NAME',        'Human Resources');

/**
 * Send IAP status notification email(s) via SendGrid.
 *
 * $app must contain: first_name, last_name, email, position_applying_for,
 *                    desired_location, application_id
 * $options (Leadership Review only): recipients (array), custom_message (string)
 *
 * Never throws — notification failure must not break submission or status save.
 */
function send_iap_notification(string $status, array $app, array $options = []): void
{
    $sg_key = envv('SENDGRID_API_KEY');
    if (empty($sg_key)) {
        error_log('IAP notify: SENDGRID_API_KEY not set — skipping for status=' . $status);
        return;
    }

    $esc      = fn($v) => htmlspecialchars((string)$v, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $first    = $app['first_name']             ?? '';
    $last     = $app['last_name']              ?? '';
    $email    = $app['email']                  ?? '';
    $position = $app['position_applying_for']  ?? '';
    $location = $app['desired_location']       ?? '';
    $app_id   = (int)($app['application_id']   ?? 0);

    // Leadership Review: separate per-recipient loop (preserves existing behavior)
    if ($status === 'Leadership Review') {
        $recipients     = $options['recipients']     ?? [];
        $custom_message = $options['custom_message'] ?? '';
        if (empty($recipients)) return;

        $detail_url = 'https://intranet.whitewatercw.com/hr/iap.php?page=detail&id=' . $app_id;
        $subject    = 'Leadership Review — ' . $esc($first) . ' ' . $esc($last);
        $body       = iap_email_wrap($subject,
                        '<h2>Leadership Review — ' . $esc($first) . ' ' . $esc($last) . '</h2>'
                      . '<p><strong>Position Applied:</strong> ' . $esc($position) . '</p>'
                      . '<p><strong>Desired Location:</strong> ' . $esc($location) . '</p>'
                      . (!empty($custom_message) ? '<p><strong>Note:</strong> ' . $esc($custom_message) . '</p>' : '')
                      . '<p><a href="' . $esc($detail_url) . '">View Application</a></p>');
        try {
            $sendgrid = new \SendGrid($sg_key);
            foreach ($recipients as $recipient_email) {
                $mail = new \SendGrid\Mail\Mail();
                $mail->setFrom(IAP_FROM_EMAIL, IAP_FROM_NAME);
                $mail->setSubject($subject);
                $mail->addTo($recipient_email);
                $mail->addContent('text/html', $body);
                $response = $sendgrid->send($mail);
                if ($response->statusCode() >= 400) {
                    error_log('IAP notify: Leadership Review failed for ' . $recipient_email
                        . ': ' . $response->statusCode() . ' ' . $response->body());
                }
            }
        } catch (\Exception $e) {
            error_log('IAP notify: Leadership Review exception: ' . $e->getMessage());
        }
        return;
    }

    // All other statuses: build subject, body, To list, CC list
    $subject   = '';
    $body_html = '';
    $to_list   = [];  // [['email' => ..., 'name' => ...], ...]
    $cc_list   = [];  // [email, ...]

    switch ($status) {
        case 'New':
            $subject   = 'Next Steps Required to Complete Your Application';
            $body_html = '<p>Hi ' . $esc($first) . ',</p>'
                       . '<p>Thank you for your interest in the opportunity! You\'re just one step away from completing your application.</p>'
                       . '<p>Please be sure to apply directly to the specific position you would like to be considered for via JazzHR.</p>'
                       . '<p>Once completed, you can expect an update regarding next steps within 72 business hours.</p>'
                       . '<p>If you have any questions, please don\'t hesitate to reach out.</p>'
                       . '<p>Best regards,<br>Human Resources</p>';
            if (!empty($email)) {
                $to_list[] = ['email' => $email, 'name' => $first . ' ' . $last];
                $cc_list   = [IAP_HR_EMAIL, IAP_RECRUITING_EMAIL];
            } else {
                error_log('IAP notify: no candidate email for app #' . $app_id . ' — sending New to HR/Recruiting only');
                $to_list[] = ['email' => IAP_HR_EMAIL,         'name' => 'HR'];
                $to_list[] = ['email' => IAP_RECRUITING_EMAIL, 'name' => 'Recruiting'];
            }
            break;

        case 'ReviewedProcessing':
            if (empty($email)) {
                error_log('IAP notify: no candidate email for app #' . $app_id . ' — skipping Processing notify');
                return;
            }
            $subject   = 'Application Status Update';
            $body_html = '<p>Hi ' . $esc($first) . ',</p>'
                       . '<p>Thank you for your application. Our submission is currently under review to confirm eligibility for the role.</p>'
                       . '<p>We appreciate your patience and will provide an update as soon as the review is complete.</p>'
                       . '<p>Best regards,<br>Human Resources</p>';
            $to_list[] = ['email' => $email, 'name' => $first . ' ' . $last];
            break;

        case 'Eligible':
            if (empty($email)) {
                error_log('IAP notify: no candidate email for app #' . $app_id . ' — skipping Eligible notify');
                return;
            }
            $subject   = 'Application Update – Eligible for Consideration';
            $body_html = '<p>Hi ' . $esc($first) . ',</p>'
                       . '<p>Congratulations! Based on our review, you meet the minimum requirements to be considered for the <strong>'
                       . $esc($position) . '</strong> at <strong>' . $esc($location) . '</strong>.</p>'
                       . '<p>Your application has been shared with the hiring manager, who will review your information and determine next steps. You will be contacted soon with an update on the next stage of the process.</p>'
                       . '<p>Best of luck, and again, thank you for your interest!</p>'
                       . '<p>Best regards,<br>Human Resources</p>';
            $to_list[] = ['email' => $email, 'name' => $first . ' ' . $last];
            break;

        case 'Ineligible':
            $subject   = 'Application Update';
            $body_html = '<p>Hi ' . $esc($first) . ',</p>'
                       . '<p>Thank you for your interest in the role. We regret to inform you that you do not meet the minimum requirements for the position you applied for at this time.</p>'
                       . '<p>A member of the HR team will follow up with you within 72 business hours to provide additional details.</p>'
                       . '<p>If you believe this decision has been made in error, please feel free to discuss your concerns directly with HR.</p>'
                       . '<p>We appreciate your interest and encourage you to apply for future opportunities that align with your qualifications.</p>'
                       . '<p>Best regards,<br>Human Resources</p>';
            if (!empty($email)) {
                $to_list[] = ['email' => $email, 'name' => $first . ' ' . $last];
                $cc_list   = [IAP_HR_EMAIL, IAP_RECRUITING_EMAIL];
            } else {
                error_log('IAP notify: no candidate email for app #' . $app_id . ' — sending Ineligible to HR/Recruiting only');
                $to_list[] = ['email' => IAP_HR_EMAIL,         'name' => 'HR'];
                $to_list[] = ['email' => IAP_RECRUITING_EMAIL, 'name' => 'Recruiting'];
            }
            break;

        default:
            return;
    }

    if (empty($to_list)) return;

    try {
        $sendgrid = new \SendGrid($sg_key);
        $mail = new \SendGrid\Mail\Mail();
        $mail->setFrom(IAP_FROM_EMAIL, IAP_FROM_NAME);
        $mail->setSubject($subject);
        foreach ($to_list as $t) {
            $mail->addTo($t['email'], $t['name']);
        }
        foreach ($cc_list as $cc_email) {
            $mail->addCc($cc_email);
        }
        $mail->addContent('text/html', iap_email_wrap($subject, $body_html));
        $response = $sendgrid->send($mail);
        if ($response->statusCode() >= 400) {
            error_log('IAP notify: send failed — status=' . $status . ' app=#' . $app_id
                . ' code=' . $response->statusCode() . ' body=' . $response->body());
        }
    } catch (\Exception $e) {
        error_log('IAP notify: exception — status=' . $status . ' app=#' . $app_id . ' ' . $e->getMessage());
    }
}

/**
 * Wrap email body content in a minimal HTML email shell.
 */
function iap_email_wrap(string $title, string $content): string
{
    $t = htmlspecialchars($title, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    return '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>' . $t . '</title></head>'
         . '<body style="font-family:Arial,sans-serif;font-size:14px;color:#333;max-width:600px;margin:0 auto;padding:20px;">'
         . $content
         . '</body></html>';
}
```

- [ ] **Step 2: Verify PHP syntax**

```bash
php -l /c/Users/Eric/Workspace/Git/whitewater-secure/app/HR/IAP/iap_notifications.php
```

Expected output: `No syntax errors detected in ...iap_notifications.php`

---

## Task 2: Update submit_application.php

**Files:**
- Modify: `app/HR/IAP/submit_application.php` lines 114–137

- [ ] **Step 1: Replace the broken SendGrid block**

In `submit_application.php`, find and replace lines 114–137 (the entire `// Send SendGrid notification to HR and manager` block):

**Remove:**
```php
    // Send SendGrid notification to HR and manager
    $sg_key = envv('SENDGRID_API_KEY');
    if (empty($sg_key)) {
        error_log('SendGrid IAP: SENDGRID_API_KEY not set — skipping notification for application #' . $application_id);
    } else {
        try {
            $mail = new \SendGrid\Mail\Mail();
            $mail->setFrom('noreply@whitewatercw.com', 'Internal Applications');
            $mail->setSubject('New Internal Application - ' . $first_name . ' ' . $last_name);
            $hr_email = 'recruiting';
            $mail->addTo($hr_email, 'HR');
            if (!empty($manager_email) && strtolower($manager_email) !== strtolower($hr_email)) {
                $mail->addTo($manager_email);
            }
            $mail->addContent('text/html', '<h2>New Application #' . $application_id . '</h2><p>' . htmlspecialchars($first_name) . ' ' . htmlspecialchars($last_name) . ' applied for ' . htmlspecialchars($position) . '</p>');
            $sendgrid = new \SendGrid($sg_key);
            $response = $sendgrid->send($mail);
            if ($response->statusCode() >= 400) {
                error_log('SendGrid IAP notification failed: ' . $response->statusCode() . ' — ' . $response->body());
            }
        } catch (\Exception $e) {
            error_log('SendGrid IAP exception: ' . $e->getMessage());
        }
    }
```

**Replace with:**
```php
    require_once __DIR__ . '/iap_notifications.php';
    send_iap_notification('New', [
        'first_name'            => $first_name,
        'last_name'             => $last_name,
        'email'                 => $email,
        'position_applying_for' => $position,
        'desired_location'      => $desired_location,
        'application_id'        => $application_id,
    ]);
```

- [ ] **Step 2: Verify PHP syntax**

```bash
php -l /c/Users/Eric/Workspace/Git/whitewater-secure/app/HR/IAP/submit_application.php
```

Expected output: `No syntax errors detected in ...submit_application.php`

- [ ] **Step 3: Smoke test — New notification**

1. Open local intranet: `http://whitewater-secure/public/hr/iap.php?page=apply`
2. Submit a test application using your own employee ID
3. Check your inbox for subject: **"Next Steps Required to Complete Your Application"**
4. Check that hr@whitewatercw.com and recruiting@whitewatercw.com appear in the CC line
5. Check Apache error log for any `IAP notify:` errors:
   ```bash
   tail -20 /c/wamp64/logs/apache_error.log
   ```

---

## Task 3: Update sync_application_status.php

**Files:**
- Modify: `app/HR/IAP/sync_application_status.php` lines 43–85

- [ ] **Step 1: Add require_once after the DB connect block**

After line 11 (`if ($conn->connect_error) ...`), add:

```php
require_once __DIR__ . '/iap_notifications.php';
```

- [ ] **Step 2: Replace the Leadership Review block with the unified notification call**

Find and remove lines 43–85 (the entire `// Send leadership notifications when status moves to Leadership Review` block):

```php
    // Send leadership notifications when status moves to Leadership Review
    if ($new_status === 'Leadership Review' && !empty($notify_recipients)) {
        // Server-side whitelist: only allow emails present in active recipients table
        $valid_emails = [];
        $validResult  = $conn->query("SELECT email FROM iap_leadership_recipients WHERE active = 1");
        while ($vr = $validResult->fetch_assoc()) {
            $valid_emails[] = $vr['email'];
        }
        $safe_recipients = array_intersect($notify_recipients, $valid_emails);

        if (!empty($safe_recipients)) {
            // Fetch applicant details for email body
            $appStmt = $conn->prepare("SELECT first_name, last_name, position_applying_for, desired_location FROM internal_applications WHERE application_id = ?");
            $appStmt->bind_param('i', $application_id);
            $appStmt->execute();
            $app_row = $appStmt->get_result()->fetch_assoc();
            $appStmt->close();

            if ($app_row) {
                $esc        = fn($v) => htmlspecialchars((string)$v, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                $detail_url = 'https://intranet.whitewatercw.com/hr/iap.php?page=detail&id=' . (int)$application_id;
                $subject    = 'Leadership Review — ' . $esc($app_row['first_name']) . ' ' . $esc($app_row['last_name']);
                $body       = '<h2>Leadership Review — ' . $esc($app_row['first_name']) . ' ' . $esc($app_row['last_name']) . '</h2>'
                            . '<p><strong>Position Applied:</strong> ' . $esc($app_row['position_applying_for']) . '</p>'
                            . '<p><strong>Desired Location:</strong> ' . $esc($app_row['desired_location']) . '</p>'
                            . (!empty($notify_message) ? '<p><strong>Note:</strong> ' . $esc($notify_message) . '</p>' : '')
                            . '<p><a href="' . $esc($detail_url) . '">View Application</a></p>';

                $sendgrid = new \SendGrid(envv('SENDGRID_API_KEY'));
                foreach ($safe_recipients as $recipient_email) {
                    $mail = new \SendGrid\Mail\Mail();
                    $mail->setFrom('noreply@whitewatercw.com', 'Internal Applications');
                    $mail->setSubject($subject);
                    $mail->addTo($recipient_email);
                    $mail->addContent('text/html', $body);
                    $response = $sendgrid->send($mail);
                    if ($response->statusCode() >= 400) {
                        error_log('SendGrid IAP leadership notify failed for ' . $recipient_email . ': ' . $response->statusCode() . ' ' . $response->body());
                    }
                }
            }
        }
    }
```

**Replace with:**
```php
    // Send status notifications
    $notifiable_statuses = ['ReviewedProcessing', 'Leadership Review', 'Eligible', 'Ineligible'];
    if (in_array($new_status, $notifiable_statuses)) {
        $appStmt = $conn->prepare(
            "SELECT first_name, last_name, email, position_applying_for, desired_location
             FROM internal_applications WHERE application_id = ?"
        );
        $appStmt->bind_param('i', $application_id);
        $appStmt->execute();
        $app_row = $appStmt->get_result()->fetch_assoc();
        $appStmt->close();

        if ($app_row) {
            $app_row['application_id'] = (int)$application_id;
            $options = [];
            if ($new_status === 'Leadership Review') {
                // Server-side whitelist: only allow emails present in active recipients table
                $valid_emails = [];
                $validResult  = $conn->query("SELECT email FROM iap_leadership_recipients WHERE active = 1");
                while ($vr = $validResult->fetch_assoc()) {
                    $valid_emails[] = $vr['email'];
                }
                $options = [
                    'recipients'     => array_intersect($notify_recipients, $valid_emails),
                    'custom_message' => $notify_message,
                ];
            }
            send_iap_notification($new_status, $app_row, $options);
        }
    }
```

- [ ] **Step 3: Verify PHP syntax**

```bash
php -l /c/Users/Eric/Workspace/Git/whitewater-secure/app/HR/IAP/sync_application_status.php
```

Expected output: `No syntax errors detected in ...sync_application_status.php`

- [ ] **Step 4: Smoke test — Processing notification**

1. Open the IAP tracker: `http://whitewater-secure/public/hr/iap.php?page=tracker`
2. Open the test application from Task 2 smoke test
3. Set status to **Processing** and click Save
4. Check candidate inbox for subject: **"Application Status Update"**
5. Verify no CC recipients (HR/Recruiting should NOT be on this one)

- [ ] **Step 5: Smoke test — Eligible notification**

1. Complete all 6 eligibility checks on the test application
2. Set status to **Eligible** and click Save
3. Check candidate inbox for subject: **"Application Update – Eligible for Consideration"**
4. Verify the position title and location appear correctly in the body

- [ ] **Step 6: Smoke test — Ineligible notification**

1. Open a second test application (or reset the first)
2. Set status to **Ineligible** and click Save
3. Check candidate inbox for subject: **"Application Update"**
4. Verify hr@whitewatercw.com and recruiting@whitewatercw.com are CC'd

- [ ] **Step 7: Smoke test — Leadership Review (regression)**

1. Complete all 6 eligibility checks on a test application
2. Set status to **Leadership Review**, check at least one recipient, click Save
3. Verify the checked recipients receive the **"Leadership Review — [Name]"** email
4. Verify the detail URL link in the email resolves correctly

- [ ] **Step 8: Check error log is clean**

```bash
tail -30 /c/wamp64/logs/apache_error.log
```

Expected: No `IAP notify:` error lines from the smoke tests above.

---

## Final Check

- [ ] All 4 statuses generate the correct email to the correct recipients
- [ ] Leadership Review behavior unchanged from before
- [ ] No `IAP notify:` errors in the Apache log
- [ ] `iap_notifications.php` is the only file containing email templates — no SendGrid calls remain directly in `submit_application.php` or `sync_application_status.php`
