dcplibrary / shoutbomb-reports
Laravel package to ingest and parse Shoutbomb report emails from Microsoft 365 Outlook using Graph API
Installs: 4
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/dcplibrary/shoutbomb-reports
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.5
- illuminate/support: ^11.0|^12.0
- microsoft/microsoft-graph: ^2.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^10.0
README
A Laravel package for reading and parsing Shoutbomb report emails from Microsoft 365 Outlook using the Microsoft Graph API. Designed specifically to integrate with the Daviess County Public Library Notices Package to track SMS and Voice notification delivery.
Overview
This package automatically:
- Connects to your Microsoft 365 Outlook mailbox
- Reads Shoutbomb report emails (daily failures, monthly summaries, delivery statistics)
- Parses failure details (opted-out patrons, invalid phone numbers, undelivered notices)
- Stores the data in your database for verification and reporting
- Integrates with your existing notice verification workflow
Why This Package?
Shoutbomb sends various report emails including failure reports and monthly summaries. This package:
- Automates the manual process of checking Shoutbomb emails
- Extracts critical information (patron ID, phone number, failure reason, delivery stats)
- Stores data in a structured format for analysis
- Integrates with your notice verification system
Features
- ✅ Microsoft Graph API Integration - Secure, OAuth2-based authentication
- ✅ Smart Parsing - Extracts recipient, failure reason, error codes, and notice-specific data
- ✅ Multiple Report Types - Handles daily SMS/Voice failures and monthly comprehensive reports
- ✅ Fuzzy Matching - Supports partial barcode matching for redacted monthly report data
- ✅ Configurable Filtering - Filter by subject, sender, folder, or read status
- ✅ Duplicate Prevention - Automatically skips already-processed emails
- ✅ Dry Run Mode - Test parsing without saving to database
- ✅ Auto-organization - Mark as read, move to folders after processing
- ✅ Extensible - Easy to customize parsing for different failure report formats
Requirements
- PHP 8.1 or higher
- Laravel 10.x or 11.x
- Microsoft 365 account with Outlook
- Azure AD application (for Graph API access)
Installation
1. Install via Composer
composer require dcplibrary/shoutbomb-reports
2. Publish Configuration
php artisan vendor:publish --tag=shoutbomb-reports-config
This creates config/shoutbomb-reports.php
3. Publish Migrations
php artisan vendor:publish --tag=shoutbomb-reports-migrations php artisan migrate
This creates the notice_failure_reports table.
Azure AD Setup
1. Register Application in Azure
- Go to Azure Portal
- Navigate to Azure Active Directory → App registrations
- Click New registration
- Name: "Outlook Failure Reports"
- Supported account types: "Accounts in this organizational directory only"
- Click Register
2. Configure API Permissions
- Go to API permissions
- Click Add a permission
- Select Microsoft Graph → Application permissions
- Add these permissions:
Mail.Read(Read mail in all mailboxes)Mail.ReadWrite(if you want to mark as read/move emails)
- Click Grant admin consent
3. Create Client Secret
- Go to Certificates & secrets
- Click New client secret
- Description: "Outlook Failure Reports"
- Expiration: Choose appropriate duration
- Click Add
- Copy the secret value immediately (you won't see it again!)
4. Get IDs
From the Overview page, copy:
- Application (client) ID
- Directory (tenant) ID
Configuration
Environment Variables
Add these to your .env file:
# Azure AD Configuration SHOUTBOMB_TENANT_ID=your-tenant-id SHOUTBOMB_CLIENT_ID=your-client-id SHOUTBOMB_CLIENT_SECRET=your-client-secret # User mailbox to monitor SHOUTBOMB_USER_EMAIL=your-email@dcplibrary.org # Email Filtering SHOUTBOMB_FOLDER=null # null for inbox, or folder name SHOUTBOMB_SUBJECT_FILTER= # Leave blank/null to process all emails SHOUTBOMB_FROM_FILTER= # Leave blank/null to disable sender filtering SHOUTBOMB_MAX_EMAILS=50 SHOUTBOMB_UNREAD_ONLY=true SHOUTBOMB_MARK_AS_READ=true SHOUTBOMB_MOVE_TO_FOLDER=null # Move to folder after processing # Storage SHOUTBOMB_FAILURE_TABLE=notice_failure_reports SHOUTBOMB_STORE_RAW=false # Store raw email content (for debugging) SHOUTBOMB_LOG_PROCESSING=true
Config File
The published config file (config/shoutbomb-reports.php) contains:
- Graph API settings - Tenant, client credentials, API version
- Filtering rules - Subject, sender, folder filters (all optional - set to null/blank to disable)
- Parsing patterns - Regex patterns for extracting data
- Storage options - Table name, logging preferences
You can customize parsing patterns for your specific failure report formats. Subject and sender filters are optional and can be left blank to process all emails from the monitored mailbox.
Using with dcplibrary/notices
This package pairs with the dcplibrary/notices package to enrich verification for SMS/Voice notices.
- Ensure this package is installed and migrations are run so the failure table exists (default:
notice_failure_reports). - In your Notices app, enable the integration via either:
.env:NOTICES_SHOUTBOMB_REPORTS_ENABLED=true(preferred explicit toggle), or.env:SHOUTBOMB_LOG_PROCESSING=true(fallback existing flag)
- Configure the table name if different:
.envSHOUTBOMB_FAILURE_TABLE=notice_failure_reports. - Optional date window (± hours) for matching failures around a notice date:
.envSHOUTBOMB_REPORTS_DATE_WINDOW_HOURS=24. - Keep running your existing Shoutbomb submissions import via FTP. This integration only provides failure/undelivered signals; submissions still identify what was sent.
Behavior in Notices:
- If a matching failure row is found (by channel, phone last-10 digits, and time window), the notice is marked as Failed with the failure reason.
- If the notice is known Submitted and no failure row is found in the window, delivery is marked Delivered (inferred).
See the Notices docs for details: docs/integrations/shoutbomb-reports.md in the Notices repo.
Usage
Basic Command
Check for new failure reports and process them:
php artisan shoutbomb:check-reports
Command Options
# Dry run - see what would be processed without saving php artisan shoutbomb:check-reports --dry-run # Limit number of emails to process php artisan shoutbomb:check-reports --limit=10 # Force mark as read (override config) php artisan shoutbomb:check-reports --mark-read
Scheduled Execution
Add to app/Console/Kernel.php:
protected function schedule(Schedule $schedule) { // Check every 15 minutes during business hours $schedule->command('shoutbomb:check-reports') ->everyFifteenMinutes() ->weekdays() ->between('8:00', '18:00'); // Or check every hour $schedule->command('shoutbomb:check-reports') ->hourly(); }
Programmatic Usage
use Dcplibrary\OutlookFailureReports\Services\GraphApiService; use Dcplibrary\OutlookFailureReports\Parsers\FailureReportParser; // Get messages $graphApi = app(GraphApiService::class); $messages = $graphApi->getMessages([ 'unread_only' => true, 'max_emails' => 10, ]); // Parse a message $parser = new FailureReportParser(); $parsedData = $parser->parse($message);
Integration with Notices Package
Linking Failure Reports to Notices
The failure reports can be linked to your notices via:
- Recipient Email - Match against patron email in notices
- Patron Identifier - Extracted phone number or patron ID
- Original Message ID - Link to original notice message ID
Example query:
use Dcplibrary\OutlookFailureReports\Models\NoticeFailureReport; // Get recent SMS failures $smsFailures = NoticeFailureReport::byNoticeType('SMS') ->recent(7) ->get(); // Get failures for specific patron $patronFailures = NoticeFailureReport::where('patron_identifier', $phoneNumber) ->orderBy('received_at', 'desc') ->get(); // Get unprocessed failures $unprocessed = NoticeFailureReport::unprocessed()->get();
Adding to Verification Workflow
You can create a custom verification step in your notices package:
// In your notices verification logic use Dcplibrary\OutlookFailureReports\Models\NoticeFailureReport; public function verifyNoticeDelivery($notice) { // Check if there's a failure report for this notice $failure = NoticeFailureReport::where('patron_identifier', $notice->patron_phone) ->where('received_at', '>=', $notice->sent_at) ->first(); if ($failure) { $notice->status = 'failed'; $notice->failure_reason = $failure->failure_reason; $notice->save(); // Mark failure report as processed $failure->markAsProcessed(); } }
Database Schema
The notice_failure_reports table contains:
| Column | Type | Description |
|---|---|---|
| id | bigint | Primary key |
| outlook_message_id | string | Unique Outlook message ID |
| original_message_id | string | Original notice message ID |
| subject | string | Email subject |
| from_address | string | Sender email |
| recipient_email | string | Failed recipient email |
| patron_identifier | string | Phone number or patron ID |
| notice_type | string | SMS, Voice, or Email |
| failure_reason | text | Why delivery failed |
| error_code | string | SMTP/Error code (e.g., 550) |
| received_at | timestamp | When failure report received |
| processed_at | timestamp | When linked to notice |
| raw_content | text | Raw email content (optional) |
| created_at | timestamp | Record creation |
| updated_at | timestamp | Record update |
Customizing Parsing
Custom Patterns
Edit config/shoutbomb-reports.php to add custom regex patterns:
'parsing' => [ 'recipient_patterns' => [ '/Your custom pattern here/i', // ... ], 'reason_patterns' => [ '/Another custom pattern/i', // ... ], ],
Extending the Parser
Create a custom parser:
use Dcplibrary\OutlookFailureReports\Parsers\FailureReportParser; class CustomFailureParser extends FailureReportParser { protected function extractNoticeSpecificData(string $content, array $message): array { $data = parent::extractNoticeSpecificData($content, $message); // Add your custom extraction logic if (preg_match('/Library Card: (\d+)/', $content, $matches)) { $data['library_card'] = $matches[1]; } return $data; } }
Troubleshooting
Common Issues
"Unauthorized" or "Access Denied"
- Verify Azure AD app permissions are granted
- Ensure admin consent was granted
- Check tenant ID and client ID are correct
"No emails found"
- Check filter settings in config
- Verify user email is correct
- Test with
--dry-runto see what would be processed
"Failed to parse"
- Enable raw content storage:
SHOUTBOMB_STORE_RAW=true - Check logs:
storage/logs/laravel.log - Adjust parsing patterns in config
Token expired
- Tokens are cached for 50 minutes
- Clear cache:
php artisan cache:clear
Debug Mode
Enable debug logging:
SHOUTBOMB_LOG_PROCESSING=true SHOUTBOMB_STORE_RAW=true LOG_LEVEL=debug
Run with dry-run to see parsed data:
php artisan shoutbomb:check-reports --dry-run
Security Considerations
- Store Azure credentials securely (use
.env, never commit) - Use application permissions (not delegated) for unattended operation
- Regularly rotate client secrets
- Monitor API usage in Azure portal
- Consider using Azure Key Vault for production secrets
Testing
Create a test failure report email and send it to your monitored inbox, then run:
php artisan shoutbomb:check-reports --dry-run
Verify the parsing is correct before running without --dry-run.
API Reference
GraphApiService
// Get access token $token = $graphApi->getAccessToken(); // Get messages with filters $messages = $graphApi->getMessages([ 'unread_only' => true, 'subject_contains' => 'Undelivered', 'max_emails' => 50, ]); // Get single message $message = $graphApi->getMessage($messageId); // Mark as read $graphApi->markAsRead($messageId); // Move to folder $graphApi->moveMessage($messageId, 'Processed'); // Get message body $body = $graphApi->getMessageBody($message, 'text');
FailureReportParser
// Parse email $parsedData = $parser->parse($message, $bodyContent); // Validate parsed data $isValid = $parser->validate($parsedData);
NoticeFailureReport Model
// Query scopes NoticeFailureReport::unprocessed()->get(); NoticeFailureReport::byNoticeType('SMS')->get(); NoticeFailureReport::recent(7)->get(); // Mark as processed $report->markAsProcessed();
Roadmap
- Support for multiple mailboxes
- Webhook support (instead of polling)
- Integration with specific Shoutbomb failure report formats
- Dashboard for viewing failure reports
- Export failure reports to CSV
- Automatic patron notification blocking
Contributing
Contributions are welcome! Please submit pull requests to the repository.
License
MIT License
Support
For issues specific to this package, please open an issue on GitHub.
For Azure AD / Graph API questions, refer to Microsoft Graph documentation.
Credits
Created by Brian Lashbrook for Daviess County Public Library.
Integrates with Daviess County Public Library Notices Package.