Messaging

The Messaging module provides a powerful system for composing, targeting, and sending bulk messages to different groups within the school community. It is designed for asynchronous sending via a background worker.

Constructor

__construct
__construct($db, $events, $mail)

Creates a new Messaging module instance with database access, event system, and mail service integration.

Parameters

$dbDatabaseYesDatabase connection instance
$eventsEventsYesEvent system for message notifications
$mailMailYesMail service for sending messages

Example

<?php
$config = [
    'db' => [
        'dsn' => 'mysql:host=localhost;dbname=schoolkit;charset=utf8mb4',
        'user' => 'your_username',
        'pass' => 'your_password'
    ],
    'storage_path' => __DIR__ . '/storage'
];

$schoolKit = SchoolKit::start($config);
$messaging = $schoolKit->messaging();

// Messaging module is now ready for bulk communication
echo "Messaging module initialized successfully";
?>

Quick Reference

create()
create(array $data): int

Creates a new message draft with subject and body

addTarget()
addTarget(int $messageId, string $scopeType, string $scopeId): bool

Adds targeting criteria to specify message recipients

expandRecipients()
expandRecipients(int $messageId): int

Resolves targets into concrete list of email addresses and returns count

schedule()
schedule(int $messageId, string $sendAt): bool

Schedules message for delivery at specific time

sendNow()
sendNow(int $messageId): int

Marks message for immediate sending by background worker and returns count

Workflow

  1. Create a Message: Use create() to compose the subject and body.
  2. Define Recipients: Use addTarget() to specify who should receive the message (e.g., a whole grade, a specific class).
  3. Expand Recipients: Use expandRecipients() to resolve the targets into a concrete list of email addresses.
  4. Schedule or Send: Use schedule() or sendNow() to mark the message for delivery by the background worker.

Template Management

createTemplate
createTemplate(string $name, string $subject, ?string $bodyHtml = null, ?string $bodyText = null, ?int $createdBy = null): int

Creates a reusable message template with separate HTML and text content for consistent communications.

Example

<?php
$templateId = $schoolKit->messaging()->createTemplate(
    'Field Trip Reminder',
    'Upcoming Field Trip - {{trip_date}}',
    '

Field Trip Reminder

Dear {{guardian_name}}, your child {{student_name}} has a field trip on {{trip_date}}.

', "Dear {{guardian_name}}, your child {{student_name}} has a field trip on {{trip_date}}.", 123 // User ID of template creator ); echo "Created template with ID: {$templateId}"; ?>

Parameters

  • string $name - The template name for identification
  • string $subject - The email subject line template
  • ?string $bodyHtml - The HTML version of the email body (optional)
  • ?string $bodyText - The plain text version of the email body (optional)
  • ?int $createdBy - User ID of the template creator (optional)

Returns

int - The ID of the newly created template

listTemplates
listTemplates(array $filters = []): array

Lists available message templates with optional filtering.

Example

<?php
// Get templates created by a specific user
$templates = $schoolKit->messaging()->listTemplates(['created_by' => 123]);

// Get all templates
$allTemplates = $schoolKit->messaging()->listTemplates();

foreach ($templates as $template) {
    echo "Template: {$template['name']} - Subject: {$template['subject']}\n";
}
?>

Parameters

  • array $filters - Optional filters (created_by, name, type, etc.)

Returns

array - Array of template records

deleteTemplate
deleteTemplate(int $templateId): bool

Permanently deletes a message template.

Example

<?php
if ($schoolKit->messaging()->deleteTemplate(15)) {
    echo "Template deleted successfully";
} else {
    echo "Failed to delete template";
}
?>

Parameters

  • int $templateId - The ID of the template to delete

Returns

bool - True on successful deletion, false otherwise

Message Composition

create
create(array $data): int

Creates a new message draft.

Parameters

NameTypeRequiredDescription
$data array Yes Associative array with subject, and either body_html or body_text.

Returns

The ID of the newly created message.

Example

<?php
$messageId = $schoolKit->messaging()->create([
    'subject' => 'School Picture Day Reminder',
    'body_html' => '

Don\'t forget, picture day is this Friday!

', 'created_by' => 123 // Admin user ID ]); echo "Created message draft with ID: {$messageId}"; ?>
addTarget
addTarget(int $messageId, string $scopeType, string $scopeId): bool

Adds a recipient group to the message. Valid scope types are: class, grade, activity.

Parameters

NameTypeRequiredDescription
$messageIdintYesThe ID of the message.
$scopeTypestringYesType of target group (class, grade, activity).
$scopeIdstringYesID of the target group.

Returns

bool - True on success, false otherwise.

Example

<?php
$messageId = 101;
// Target all guardians of students in grade 5
$schoolKit->messaging()->addTarget($messageId, 'grade', '5');

// Also target all guardians of students in the chess club (activity ID 22)
$schoolKit->messaging()->addTarget($messageId, 'activity', '22');
?>
addManual
addManual(int $messageId, string $email, string $name = ''): bool

Adds a single, manual email address to the message recipients.

Example

<?php
$messageId = 101;
$schoolKit->messaging()->addManual($messageId, 'special.guest@example.com', 'Special Guest');
$schoolKit->messaging()->addManual($messageId, 'board.member@school.edu', 'Board Member');

echo "Added manual recipients to the message";
?>

Parameters

  • int $messageId - The ID of the message
  • string $email - The email address to add
  • string $name - Optional display name for the recipient

Returns

bool - True on successful addition, false otherwise

attach
attach(int $messageId, string $filePath, ?string $name = null, ?string $mime = null): bool

Attaches a file to a message for delivery to all recipients.

Example

<?php
$messageId = 101;

// Attach a PDF with custom name
$schoolKit->messaging()->attach($messageId, '/uploads/permission_slip.pdf', 'Permission Slip.pdf');

// Attach an image (MIME type auto-detected)
$schoolKit->messaging()->attach($messageId, '/uploads/school_map.png');

echo "Files attached to message";
?>

Parameters

  • int $messageId - The ID of the message
  • string $filePath - Path to the file to attach
  • ?string $name - Optional display name for the attachment
  • ?string $mime - Optional MIME type (auto-detected if not provided)

Returns

bool - True on successful attachment, false otherwise

Sending & Scheduling

expandRecipients
expandRecipients(int $messageId): int

Resolves all targets (like grades and classes) into a final list of individual email recipients. This should be done before sending.

Parameters

NameTypeRequiredDescription
$messageIdintYesThe ID of the message.

Returns

The number of unique recipients added to the queue.

Example

<?php
$messageId = 101;
$count = $schoolKit->messaging()->expandRecipients($messageId);
echo "Message is ready to be sent to {$count} recipients.";
?>
sendNow
sendNow(int $messageId): int

Marks a message to be sent as soon as possible by the background worker.

Parameters

NameTypeRequiredDescription
$messageIdintYesThe ID of the message to send.

Returns

The number of pending recipients for this message.

Example

<?php
$messageId = 101;
$pendingCount = $schoolKit->messaging()->sendNow($messageId);
echo "Message queued for immediate delivery to {$pendingCount} recipients.";
?>
schedule
schedule(int $messageId, string $sendAt): bool

Schedules a message to be sent at a future time by the background worker.

Parameters

NameTypeRequiredDescription
$messageIdintYesThe ID of the message to schedule.
$sendAtstringYesA valid date-time string (e.g., '2025-09-01 09:00:00').

Returns

bool - True on success, false otherwise.

Example

<?php
$messageId = 101;
$schoolKit->messaging()->schedule($messageId, '2025-08-30 17:00:00');
echo "Message scheduled for delivery.";
?>
status
status(int $messageId): array

Gets the delivery status counts for a message.

Example

<?php
$status = $schoolKit->messaging()->status(101);

echo "Message Status:\n";
echo "Total Recipients: {$status['totals']}\n";
echo "Sent: {$status['sent']}\n";
echo "Failed: {$status['failed']}\n";
echo "Pending: {$status['pending']}\n";
?>

Parameters

  • int $messageId - The ID of the message to check

Returns

array - Status counts with keys: totals, sent, failed, pending

dryRun
dryRun(int $messageId): array

Performs a dry run simulation to preview who would receive a message without actually sending it.

Example

<?php
$dryRunResults = $schoolKit->messaging()->dryRun(101);
echo "Subject: {$dryRunResults['subject']}\n";
echo "Would send to " . count($dryRunResults['to']) . " recipients:\n";

foreach ($dryRunResults['to'] as $email) {
    echo "• {$email}\n";
}

if (isset($dryRunResults['preview_html'])) {
    echo "\nHTML Preview:\n{$dryRunResults['preview_html']}\n";
}

if (isset($dryRunResults['preview_text'])) {
    echo "\nText Preview:\n{$dryRunResults['preview_text']}\n";
}
?>

Parameters

  • int $messageId - The ID of the message to test

Returns

array - Dry run results with keys: 'subject', 'to' (array of emails), 'preview_html' (optional), 'preview_text' (optional)

unsubscribe
unsubscribe(string $email): bool

Permanently unsubscribes an email address from receiving bulk messages.

Example

<?php
$email = $_GET['email'] ?? '';
$token = $_GET['token'] ?? '';

// Verify unsubscribe token first for security
if (hash_equals($expected_token, $token)) {
    if ($schoolKit->messaging()->unsubscribe($email)) {
        echo "You have been successfully unsubscribed from bulk messages";
    } else {
        echo "Unable to process unsubscribe request";
    }
} else {
    echo "Invalid unsubscribe link";
}
?>

Parameters

  • string $email - The email address to unsubscribe

Returns

bool - True on successful unsubscription, false otherwise

Worker Methods

These methods are intended for use by the background sending script (e.g., bin/send.php).

getPendingMessages
getPendingMessages(): array

Gets all messages that are due for sending and have pending recipients (used by background workers).

Example

<?php
// Background worker script
$pendingMessages = $schoolKit->messaging()->getPendingMessages();

foreach ($pendingMessages as $message) {
    echo "Processing message ID: {$message['id']} - '{$message['subject']}'\n";
    
    // Process recipients for this message
    $recipients = $schoolKit->messaging()->getPendingRecipients($message['id'], 25);
    // ... send emails to recipients
}
?>

Returns

array - Array of message records ready for processing by the worker

getPendingRecipients
getPendingRecipients(int $messageId, int $limit = 50): array

Gets a batch of pending recipients for a specific message (used by background workers).

Example

<?php
$recipients = $schoolKit->messaging()->getPendingRecipients(101, 25);

foreach ($recipients as $recipient) {
    // Attempt to send email
    try {
        $schoolKit->mail()->send([
            'to' => $recipient['email'],
            'subject' => $recipient['subject'],
            'html' => $recipient['body_html']
        ]);
        $schoolKit->messaging()->markSent($recipient['id']);
    } catch (Exception $e) {
        $schoolKit->messaging()->markFailed($recipient['id'], $e->getMessage());
    }
}
?>

Parameters

  • int $messageId - The ID of the message
  • int $limit - Maximum number of recipients to fetch (default: 50)

Returns

array - Array of recipient records ready for sending

markSent
markSent(int $recipientId): bool

Marks a recipient's delivery status as 'sent' (used by background workers).

Example

<?php
// After successfully sending an email
if ($schoolKit->messaging()->markSent($recipientId)) {
    echo "Recipient marked as sent";
} else {
    echo "Failed to update recipient status";
}
?>

Parameters

  • int $recipientId - The ID of the recipient record

Returns

bool - True on successful status update, false otherwise

markFailed
markFailed(int $recipientId, string $error): bool

Marks a recipient's delivery status as 'failed' and logs the error (used by background workers).

Example

<?php
// After a failed email send attempt
if ($schoolKit->messaging()->markFailed($recipientId, "SMTP connection timeout")) {
    echo "Recipient marked as failed with error logged";
} else {
    echo "Failed to update recipient status";
}
?>

Parameters

  • int $recipientId - The ID of the recipient record
  • string $error - The error message to log

Returns

bool - True on successful status update, false otherwise

getMessageAttachments
getMessageAttachments(int $messageId): array

Retrieves all attachments for a specific message (used by background workers).

Example

<?php
$attachments = $schoolKit->messaging()->getMessageAttachments(101);

foreach ($attachments as $attachment) {
    echo "File: {$attachment['name']} ({$attachment['mime']})\n";
    echo "Path: {$attachment['file_path']}\n";
    echo "Size: " . number_format($attachment['size'] / 1024, 2) . " KB\n";
}
?>

Parameters

  • int $messageId - The ID of the message

Returns

array - Array of attachment records with file paths, names, and MIME types

shouldRetry
shouldRetry(array $recipient): bool

Determines whether a failed recipient should be retried based on failure reason and retry count (used by background workers).

Example

<?php
// Background worker logic
$recipients = $schoolKit->messaging()->getPendingRecipients(101, 25);

foreach ($recipients as $recipient) {
    try {
        // Attempt to send email
        $sent = $schoolKit->mail()->send([
            'to' => $recipient['email'],
            'subject' => $recipient['subject'],
            'html' => $recipient['body_html']
        ]);
        
        if ($sent) {
            $schoolKit->messaging()->markSent($recipient['id']);
        } else {
            throw new Exception("SMTP failed");
        }
    } catch (Exception $e) {
        // Check if we should retry this recipient
        if ($schoolKit->messaging()->shouldRetry($recipient)) {
            // Will be retried in next worker cycle
            continue;
        } else {
            // Mark as permanently failed
            $schoolKit->messaging()->markFailed($recipient['id'], $e->getMessage());
        }
    }
}
?>

Parameters

  • array $recipient - The recipient record containing status and retry information

Returns

bool - True if the recipient should be retried, false if permanently failed

markSentBatch
markSentBatch(array $recipientIds): bool

Marks multiple recipients as sent in a single batch operation (used by background workers).

Example

<?php
// After successfully sending multiple emails
$sentIds = [123, 124, 125, 126];
$success = $schoolKit->messaging()->markSentBatch($sentIds);
if ($success) {
    echo "All recipients marked as sent successfully";
} else {
    echo "Some recipients could not be marked as sent";
}
?>

Parameters

  • array $recipientIds - Array of recipient IDs to mark as sent

Returns

bool - True if all recipients were successfully updated, false otherwise

markFailedBatch
markFailedBatch(array $failures): bool

Marks multiple recipients as failed with their respective error messages in a single batch operation (used by background workers).

Example

<?php
// After multiple failed email attempts
$failures = [
    ['id' => 127, 'error' => 'Invalid email address'],
    ['id' => 128, 'error' => 'SMTP timeout'],
    ['id' => 129, 'error' => 'Recipient blocked']
];

$success = $schoolKit->messaging()->markFailedBatch($failures);
if ($success) {
    echo "All recipients marked as failed successfully";
} else {
    echo "Some recipients could not be marked as failed";
}
?>

Parameters

  • array $failures - Array of recipient data with 'id' and 'error' keys

Returns

bool - True if all recipients were successfully updated, false otherwise

All Available Methods

Constructor

Template Management (3 methods)

Message Composition (4 methods)

Sending & Scheduling (6 methods)

Worker Methods (7 methods)

Total: 22 methods documented (including constructor)