nexiom / connect-php
The official Nexiom Connect SDK for PHP
Requires
- php: ^8.2
- ext-json: *
- guzzlehttp/guzzle: ^7.9 || ^8.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5 || ^12.5 || ^13.4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-14 22:43:34 UTC
README
The official PHP SDK for Nexiom Connect. Send emails and manage contacts from PHP 8.2 or later.
Install
composer require nexiom/connect-php
Requires PHP 8.2 or later and Guzzle 7.9 or 8. Keep your API key on the server.
Initialize
use Nexiom\Connect\NexiomConnect; $nexiomConnect = new NexiomConnect('nc_your_api_key_here'); // Optional custom host: // $nexiomConnect = new NexiomConnect( // apiKey: 'nc_...', // baseUrl: 'https://api-connect.nxiom.com/api', // );
Send email
Use a verified sending domain and an API key with email-send or full-access permission.
use Nexiom\Connect\Exceptions\NexiomException; try { $email = $nexiomConnect->emails->send([ 'from' => 'hello@your-verified-domain.com', 'fromName' => 'Your team', 'to' => 'customer@example.com', 'subject' => 'Welcome', 'html' => '<p>Thanks for joining us.</p>', 'text' => 'Thanks for joining us.', ]); echo $email['messageId']; } catch (NexiomException $error) { error_log($error->getMessage()); }
Use a bare email address for from and fromName for the display name. A successful response means the email is queued and includes messageId, deliveryIds, and totalQueued.
You can also send a published template with templateId and templateVariables. Optional fields include replyTo, cc, bcc, and metadata. Recipients can be one address or an array of up to 100 addresses; CC and BCC require one primary recipient.
Idempotency
For retries across separate calls or jobs, use the same key and parameters:
$nexiomConnect->emails->send( [ 'from' => 'orders@your-verified-domain.com', 'to' => 'customer@example.com', 'subject' => 'Order received', 'text' => 'We received your order.', ], ['idempotencyKey' => 'order-123-confirmation'], );
Otherwise, the SDK generates a key for each call and reuses it for automatic retries. Keys contain 1–128 printable ASCII characters without whitespace. If a send fails, the key is available as $error->response->idempotencyKey, including after a timeout.
Scheduled emails
Pass scheduledAt (a DateTimeInterface or an ISO 8601 timestamp with an offset, at most 30 days ahead) to send later. Use the returned messageId to move or cancel it before it is sent.
$email = $nexiomConnect->emails->send([ 'from' => 'hello@your-verified-domain.com', 'to' => 'customer@example.com', 'subject' => 'Your trial ends tomorrow', 'text' => 'Your trial ends tomorrow.', 'scheduledAt' => new DateTimeImmutable('+1 day'), ]); $nexiomConnect->emails->reschedule($email['messageId'], [ 'scheduledAt' => '2026-10-06T09:00:00+01:00', ]); $nexiomConnect->emails->cancel($email['messageId']);
Email logs
| Method | Description |
|---|---|
list($params = []) |
List recipient deliveries, newest first |
get($deliveryId) |
Get one delivery with its content and history |
Both require a full-access API key.
$page = $nexiomConnect->emails->list(['status' => 'bounced', 'limit' => 50]); foreach ($page['items'] as $delivery) { echo $delivery['id'], ' ', $delivery['recipient'], ' ', $delivery['status'], PHP_EOL; } // Next page: pass $page['nextCursor'] as 'cursor'; it is null on the last page.
list() also accepts source, recipient, search, contactId, templateId, startDate, and endDate. get() takes a delivery ID from deliveryIds or list(), not a message ID.
Suppressions
emails->suppressions->list() lists the addresses this project cannot send to, alphabetically, with the reason. It requires a full-access API key.
$page = $nexiomConnect->emails->suppressions->list(['search' => 'example.com', 'limit' => 50]); foreach ($page['items'] as $suppression) { echo $suppression['email'], ' ', $suppression['reason'], PHP_EOL; } // Next page: pass $page['nextCursor'] as 'cursor' while $page['hasMore'] is true.
Reasons are hard_bounce, complaint, unsubscribed, invalid, manual, and temporary_failure. search matches part of an address.
Contacts
Contact methods require a full-access API key.
| Method | Description |
|---|---|
create($params) |
Create a contact |
list($params = []) |
List or search contacts |
get($id) |
Get a contact |
update($id, $params) |
Update a contact |
delete($id) |
Delete a contact |
$contact = $nexiomConnect->contacts->create([ 'email' => 'ada@example.com', 'firstName' => 'Ada', 'lastName' => 'Lovelace', 'properties' => ['company' => 'Example'], ]); echo $contact['id'];
Creation also accepts userId and phoneNumber. Updates require email and accept emailStatus (subscribed or unsubscribed); 'userId' => null clears the external user ID.
list() accepts page (up to 10,000), limit, search, emailStatus, listId, and segmentId. It returns items, total, page, and limit, with a default page size of 50 and a maximum of 100.
Responses use snake_case fields such as first_name and created_at. Dates are ISO 8601 strings. get($id) also returns properties and activity.
Contact properties
| Method | Description |
|---|---|
create(['name' => ..., 'type' => ..., 'fallbackValue' => ...]) |
Create a property |
list($params = []) |
List or filter properties |
update($id, ['name' => ..., 'type' => ..., 'fallbackValue' => ...]) |
Rename a property, change its type, or set its fallback |
delete($id) |
Delete a property |
$property = $nexiomConnect->contacts->properties->create([ 'name' => 'company', 'type' => 'string', 'fallbackValue' => 'Unknown', ]); echo $property['id'], ' ', $property['key'];
Types are string, number, and date. A type can change only while no contact has a value for the property. Fallback values are strings or null. Responses use key and fallback_value. Optional type and case-insensitive search filters are applied locally to the complete property list.
Templates
Template methods are read-only and require a full-access API key. Create and edit templates in the dashboard.
| Method | Description |
|---|---|
list($params = []) |
List templates, most recently updated first |
get($templateId) |
Get a template with its published and draft versions |
variables($templateId) |
List the variables of the draft, or of the published version |
versions($templateId, $params = []) |
List versions, newest first |
$templates = $nexiomConnect->templates->list(['status' => 'published', 'limit' => 50]); $template = $nexiomConnect->templates->get($templates['items'][0]['id']); foreach ($template['published_version']['variables'] ?? [] as $variable) { echo $variable['key'], $variable['required'] ? ' (required)' : '', PHP_EOL; }
list() accepts page, limit, status (draft, published, changes_in_draft, or archived), search, origin (custom or prebuilt), and category. Sends use the published version, so published_version.variables lists exactly what a send needs. versions() accepts limit and beforeVersion: pass the last version_number to read older versions.
Sending domains
Domain methods require a full-access API key.
| Method | Description |
|---|---|
create($params) |
Add a sending subdomain and get its DNS records |
list($params = []) |
List sending domains |
get($domainId) |
Get a domain with its DNS records |
verify($domainId) |
Check the domain's DNS records now |
delete($domainId) |
Delete a domain |
$domain = $nexiomConnect->domains->create(['domain' => 'mail.example.com']); foreach ($domain['dns_records'] as $record) { echo $record['record_type'], ' ', $record['name'], ' ', $record['value'], PHP_EOL; } // After publishing the records with your DNS provider: $domain = $nexiomConnect->domains->verify($domain['id']); echo $domain['status'];
Sending domains must be subdomains, such as mail.example.com. create() also accepts openTracking (default true). list() accepts page, limit, status (pending, verified, or failed), and search. A successful verify() means the check ran; read status before sending.
Messaging
Send messages on a messaging channel with one method; channel selects the parameters. WhatsApp is the first channel. The API key needs the whatsapp.send or whatsapp.full_access permission to send WhatsApp messages.
| Method | Description |
|---|---|
messaging->send($params, $options = []) |
Send a message to one recipient |
messaging->get($messageId) |
Get a message with its status, cost, and content |
$message = $nexiomConnect->messaging->send( [ 'channel' => 'whatsapp', 'from' => '+213555000111', 'to' => '+213555123456', 'template' => [ 'id' => 'wat_your_template_id', 'variables' => [ 'body' => [1 => 'Amina', 2 => 'A-1234'], ], ], ], ['idempotencyKey' => 'order-A-1234-shipped'], ); echo $message['id'], ' ', $message['status'];
For WhatsApp, from is the phone number of a connected WhatsApp number and to the recipient, both in E.164 format. template['id'] is an approved template, and template['variables']['header'] and ['body'] give a string for every variable of the template, keyed by its number (1) or name ('first_name'); they are always sent as JSON objects. Omit variables for a template without variables.
Every send carries an idempotency key; the SDK generates one when you don't pass idempotencyKey, and it is on $e->response->idempotencyKey when a send fails. Sends are retried with the same key, so a message is sent at most once. A send with a key already used returns the original message as it is now.
A sent WhatsApp message usually has the status accepted: WhatsApp accepted it, which is not delivery yet. uncertain means WhatsApp did not confirm it; it is never resent automatically. Follow the status with messaging->get($messageId) or the whatsapp.message.* webhooks.
Webhooks
webhooks->constructEvent($payload, $headers, $secret, tolerance: 300) checks a webhook request's signature and timestamp, then returns the event as an array. It makes no network request.
// routes/api.php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Nexiom\Connect\Exceptions\NexiomWebhookVerificationException; use Nexiom\Connect\NexiomConnect; Route::post('/webhooks/nexiom', function (Request $request) { $nexiomConnect = new NexiomConnect(config('services.nexiom.key')); try { $event = $nexiomConnect->webhooks->constructEvent( $request->getContent(), $request->headers->all(), config('services.nexiom.webhook_secret'), ); } catch (NexiomWebhookVerificationException $exception) { return response($exception->reason, 400); } if ($event['type'] === 'email.bounced') { logger()->warning('Bounced: ' . $event['data']['recipient']); } return response()->noContent(); });
$payloadis the raw request body, exactly as received:$request->getContent()in Laravel or Symfony, orfile_get_contents('php://input'). Decoded and re-encoded JSON does not match the signature.$headersaccepts$request->headers->all()(lists of values) orgetallheaders(); names are case-insensitive.$secretis the endpoint's signing secret, starting withwhsec_.toleranceis the allowed clock difference in seconds (default 300).
A request that fails verification throws Nexiom\Connect\Exceptions\NexiomWebhookVerificationException, whose reason is missing_headers, invalid_timestamp, timestamp_out_of_tolerance, invalid_signature, or invalid_payload (also available as constants on the class). Respond 400 and do not process it.
Parameters
Parameters are arrays with the camelCase keys shown above. An unknown key, such as from_name, throws NexiomValidationException instead of being ignored. A null value is treated as not set, except userId on contact updates and fallbackValue, where null clears the value.
Request options
The default base URL is https://api-connect.nxiom.com/api. Custom URLs exclude /v1 and use HTTPS, except for local development on loopback hosts.
Configure timeout (seconds, default 30), maxRetries (default 2), or a Guzzle httpClient when creating the client. Each method also accepts ['timeout' => ..., 'maxRetries' => ...] as its final argument.
$nexiomConnect = new NexiomConnect( apiKey: 'nc_...', timeout: 10, maxRetries: 3, ); $contacts = $nexiomConnect->contacts->list([], ['timeout' => 5]);
The timeout covers the entire request, including retries. Reads, email and message sends, cancels, and domain verification retry network errors and HTTP 408, 429, 500, 502, 503, and 504, honoring Retry-After. When the wait would outlast the timeout, the SDK throws that API error at once. Reschedules, domain creation and deletion, and contact and property mutations are not automatically retried. The SDK never follows redirects, so your API key is only sent to the configured host.
Errors
Methods return the response data as an array. API and transport failures throw Nexiom\Connect\Exceptions\NexiomException:
| Property | Description |
|---|---|
kind |
ErrorKind::Api, Network, Timeout, or Protocol |
getMessage() |
The API's error message, or a description of the failure |
status |
HTTP status, or null when no response was received |
errorCode |
Machine-readable API error code, such as rate_limited |
requestId |
Request ID to share with support |
details |
Additional API error details, such as validation failures |
response |
Status, headers, request ID, and the send's idempotency key |
Invalid SDK configuration or locally validated arguments throw Nexiom\Connect\Exceptions\NexiomValidationException before any request is sent.
use Nexiom\Connect\Exceptions\ErrorKind; use Nexiom\Connect\Exceptions\NexiomException; try { $nexiomConnect->contacts->get('ct_123'); } catch (NexiomException $error) { if ($error->kind === ErrorKind::Api && $error->status === 404) { // The contact does not exist. } }
Examples
- Send an email
- Create a contact
- Create a contact property
- List published templates
- Add a sending domain
- Send a WhatsApp message
- Verify a webhook request
Development
composer install composer check
License
Apache License 2.0. Copyright 2026 Nexiom Technologies.