lanefulhq / laneful-php
PHP client library for the Laneful email API
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
- squizlabs/php_codesniffer: ^3.7
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-14 16:25:57 UTC
README
A PHP client library for the Laneful email API.
Installation
composer require lanefulhq/laneful-php
Requirements: PHP 8.1+
Quick Start
<?php require_once 'vendor/autoload.php'; use Laneful\LanefulClient; use Laneful\Models\Email; use Laneful\Models\Address; use Laneful\Models\TrackingSettings; $client = new LanefulClient( baseUrl: 'https://your-endpoint.send.laneful.net', authToken: 'your-auth-token' ); $email = new Email( from: new Address('sender@example.com', 'Your Name'), to: [new Address('recipient@example.com', 'Recipient Name')], subject: 'Hello from Laneful', textContent: 'This is a test email.', htmlContent: '<h1>This is a test email.</h1>', tracking: new TrackingSettings(opens: true, clicks: true) ); try { $response = $client->sendEmail($email); echo "Email sent successfully\n"; } catch (\Laneful\Exceptions\LanefulException $e) { echo "Failed to send email: " . $e->getMessage() . "\n"; }
Features
- Send single or multiple emails
- Plain text and HTML content
- Email templates with dynamic data
- File attachments
- Email tracking (opens, clicks, unsubscribes)
- Custom headers and reply-to addresses
- Visible
from_headerand request-levelmail_settings - Scheduled sending
- Webhook signature verification
- Domain management (list, create, verify, update email track, delete)
- Unsubscribe groups
- Deliverability analytics (spam-ratio radar, Google Postmaster, Microsoft SNDS)
Examples
Template Email
$email = new Email( from: new Address('sender@example.com'), to: [new Address('user@example.com')], subject: 'Welcome to Our Service', templateId: 'welcome-template', templateData: [ 'name' => 'John Doe', 'company' => 'Acme Corp', ], tracking: new TrackingSettings(opens: true, clicks: true) ); $response = $client->sendEmail($email);
Email with Attachments
use Laneful\Models\Attachment; $email = new Email( from: new Address('sender@example.com'), to: [new Address('user@example.com')], subject: 'Document Attached', textContent: 'Please find the document attached.', attachments: [ Attachment::fromFile('/path/to/document.pdf'), ], tracking: new TrackingSettings(opens: true, clicks: true) ); $response = $client->sendEmail($email);
Email with Tracking
use Laneful\Models\TrackingSettings; $email = new Email( from: new Address('sender@example.com'), to: [new Address('user@example.com')], subject: 'Tracked Email', htmlContent: '<p>This email is tracked.</p>', tracking: new TrackingSettings( opens: true, clicks: true, unsubscribes: true ) ); $response = $client->sendEmail($email);
Web Example (Browser-based Testing)
To run the interactive web example in your browser:
# Start the web server (nginx + php-fpm) make web # Open your browser and navigate to: # http://localhost:8080/examples/web_example.php
Webhook Verification
use Laneful\Webhooks\WebhookVerifier; // Get the raw payload $payload = file_get_contents('php://input'); // Extract signature from headers (handles multiple formats) $signature = WebhookVerifier::extractSignatureFromHeaders($_SERVER); $secret = 'your-webhook-secret'; if ($signature && WebhookVerifier::verifySignature($secret, $payload, $signature)) { // Parse and validate webhook payload try { $webhookData = WebhookVerifier::parseWebhookPayload($payload); // Process events (supports both batch and single event formats) foreach ($webhookData['events'] as $event) { switch ($event['event']) { case 'request': // Handle send request accepted break; case 'delivery': // Handle email delivered break; case 'open': // Handle email opened break; case 'click': // Handle link clicked break; case 'bounce': // Handle email bounced break; // Add other event types as needed } } http_response_code(200); echo "OK"; } catch (\InvalidArgumentException $e) { http_response_code(400); echo "Invalid webhook payload: " . $e->getMessage(); } } else { http_response_code(401); echo "Invalid webhook signature\n"; }
Each parsed event contains every field Laneful sends. Every event has id, event, email, message_id, lane_id, tag, timestamp and metadata (the webhookData sent with the email); lane_id is empty on request events. Event-specific fields such as url, client_ip, is_machine, code, is_hard, classification, reason and mx_host are included too. id is unique per event and stays the same when Laneful delivers the event again on retry or in a batch, so use it to skip events you have already processed. Events of types the SDK doesn't list are kept rather than rejecting the payload, so skip the ones you don't handle.
See examples/webhook_example.php for a runnable receiver: LANEFUL_WEBHOOK_SECRET=your-webhook-secret php -S 0.0.0.0:8000 examples/webhook_example.php.
Error Handling
use Laneful\Exceptions\{ApiException, HttpException, ValidationException}; try { $response = $client->sendEmail($email); } catch (ValidationException $e) { // Invalid input data echo "Validation error: " . $e->getMessage(); } catch (ApiException $e) { // API returned an error echo "API error: " . $e->getMessage(); } catch (HttpException $e) { // Network or HTTP-level error echo "HTTP error: " . $e->getMessage(); }
API Reference
Multiple Emails
$response = $client->sendEmails([$email1, $email2, $email3]);
Visible From header
$email = new Email( from: new Address('sender@example.com', 'Your Name'), to: [new Address('user@example.com')], subject: 'Hello', textContent: 'Hello', fromHeader: new Address('newsletter@example.com', 'Newsletter') );
Mail settings (sandbox and message IDs)
use Laneful\Models\MailSettings; $response = $client->sendEmail( $email, new MailSettings(sandboxMode: true, returnMessageIds: true) ); // $response['message_ids'] is present when returnMessageIds is true
Duplicate send protection
Pass an idempotency key (1 to 36 bytes) and reuse it when you retry a send, for example after a timeout. The client does not retry on its own.
use Laneful\Models\MailSettings; $response = $client->sendEmail( $email, new MailSettings(idempotencyKey: "order-{$orderId}") );
Laneful remembers a key per organization for one hour. A repeated request with the same key gets status accepted with no message IDs and sends nothing, even if the emails differ, so use a new key for every distinct send. Detection is best effort. A request that fails with HTTP 500 doesn't keep its key, so a retry with it is processed as a new request: before resending after a 500, check your webhook events (a request event means Laneful accepted the email). Sandbox mode requests are not deduplicated. A key outside 1 to 36 bytes throws Laneful\Exceptions\ValidationException.
Tracking with an unsubscribe group
$tracking = new TrackingSettings( opens: true, clicks: true, unsubscribes: true, unsubscribeGroupId: 123, // ignored if unsubscribeGroupId is set unsubscribeGroupName: 'Newsletters' );
Custom Timeout
$client = new LanefulClient( baseUrl: $baseUrl, authToken: $authToken, timeout: 60 // seconds );
Scheduled Email
$email = new Email( from: new Address('sender@example.com'), to: [new Address('user@example.com')], subject: 'Scheduled Email', textContent: 'This email was scheduled.', sendTime: time() + (24 * 60 * 60), // 24 hours from now tracking: new TrackingSettings(opens: true, clicks: true) );
Domain, unsubscribe groups, and analytics
These endpoints live on the organization API host. Point the client at it:
$client = new LanefulClient( baseUrl: 'https://api.laneful.net', authToken: 'your-auth-token' );
Unsubscribe groups
use Laneful\Models\ListUnsubscribeGroupsParams; $groups = $client->listUnsubscribeGroups(42, new ListUnsubscribeGroupsParams(limit: 50)); $created = $client->createUnsubscribeGroup(42, 'Newsletters'); $updated = $client->updateUnsubscribeGroup(42, $created->unsubscribeGroupId, 'Weekly Newsletters');
Domains
use Laneful\Models\CreateDomainRequest; use Laneful\Models\ListDomainsParams; use Laneful\Models\UpdateDomainRequest; $list = $client->listDomains(42, new ListDomainsParams(limit: 50)); $domain = $client->createDomain(42, new CreateDomainRequest( domain: 'mydomain.com', tracking: 'tracking', returnPath: 'return-path' )); $domain = $client->getDomain(42, 'mydomain.com'); $domain = $client->verifyDomain(42, 'mydomain.com'); // Set the email track; pass '' to clear it, or omit emailTrackId to leave it unchanged $domain = $client->updateDomain(42, 'mydomain.com', new UpdateDomainRequest( emailTrackId: 'e59f0a35-05bc-4516-b585-c06f69c3e67e' )); $client->deleteDomain(42, 'mydomain.com');
Deliverability analytics
use Laneful\Models\ListDomainSpamRatioRadarParams; use Laneful\Models\ListGooglePostmasterSpamReportsParams; use Laneful\Models\ListSndsReportsParams; $radar = $client->listDomainSpamRatioRadar(new ListDomainSpamRatioRadarParams( workspaceIds: [1, 2], domain: 'example.com', startDate: '2026-09-01', endDate: '2026-09-08' )); $postmaster = $client->listGooglePostmasterSpamReports( new ListGooglePostmasterSpamReportsParams(domain: 'example.com') ); $snds = $client->listSndsReports(new ListSndsReportsParams(ip: '203.0.113.5'));