upanupstudios / upaknee-php-client
Upaknee API client for PHP
Package info
github.com/upanupstudios/upaknee-php-client
pkg:composer/upanupstudios/upaknee-php-client
Requires
- php: >=8.1
- ext-dom: *
- ext-libxml: *
- ext-simplexml: *
- php-http/discovery: ^1.19
- psr/http-client: ^1.0
- psr/http-client-implementation: *
- psr/http-factory: ^1.0
- psr/http-factory-implementation: *
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.5
- nyholm/psr7: ^1.8
- php-http/mock-client: ^1.6
- phpunit/phpunit: ^10.5
Suggests
- guzzlehttp/guzzle: A PSR-18 HTTP client and PSR-17 factory implementation (any compatible implementation works)
README
A PHP client for the Upaknee (Cloud Messaging Stack Enterprise) REST API.
The client is scoped to what the Upaknee Mailout Drupal module needs: checking a token, reading the list's sending identity, resolving the recipients of a newsletter, and submitting a mailing. It is not a full binding of the API.
Requirements
- PHP 8.1+
- A PSR-18 HTTP client and PSR-17 factories. The library does not depend on a specific implementation — install any one you like, for example Guzzle:
composer require upanupstudios/upaknee-php-client guzzlehttp/guzzle
Authentication
The API uses HTTP Basic authentication. Your token (a 32-character alphanumeric string) and an optional security password are obtained from the Settings tab of Campaign Manager.
Usage
The HTTP client and factories are auto-discovered from whatever PSR-18/PSR-17 implementation you have installed:
use Upanupstudios\Upaknee\Php\Client\Upaknee; $client = Upaknee::create('YOUR_API_TOKEN', 'OPTIONAL_PASSWORD'); // Verify a token — GET /version $client->version(); // ['message' => 'API Version: 2.1.1.3']
Lists
A token is scoped to one contact list, which carries the account's sending identity.
// The list this token belongs to — GET /subscribers/summary, then GET /lists/{id} $list = $client->lists()->current(); $list['name']; // 'Primary Contact List' $list['sender-name']; // 'Auto Assign QA' $list['sender-email']; // 'qa-auto-assign@upaknee.com' // Or address a list directly $client->lists()->show('1851712'); // Subscriber counts by status — GET /subscribers/summary $client->lists()->summary(); // ['active' => '10', 'pending' => '11', 'size' => '32', …]
Note: the sending identity is read-only. There is no route for changing a list, so the from name and from address are configured in Campaign Manager. The API models no reply-to address at all — it appears nowhere in the documented endpoints.
Newsletters
A newsletter is the subscription list a subscriber opts in to, and is the closest thing the API offers to a "group" of recipients.
// Total newsletters on the account — GET /newsletters-count $client->newsletters()->count(); // Subscriptions belonging to a newsletter — GET /newsletters/{id}/subscriptions $subscriptions = $client->newsletters()->subscriptions('15'); $subscriptions[0]['profile-id']; // 'resubtest3' $subscriptions[0]['name']; // 'general-communications' $subscriptions[0]['subscription-status']; // 'active' // Optional filters $client->newsletters()->subscriptions('15', status: 'active', limit: 500, offset: 0); // Just the recipients, active by default $profileIds = $client->newsletters()->profileIds('15'); // ['resubtest3', 'resubtest5']
subscriptions() always returns a list, including when the newsletter has exactly one subscriber —
XML cannot distinguish a one-element collection from a single object, so the client normalizes it.
Note: the API publishes no route for listing the newsletters on an account — only the total count, and the subscriptions of a newsletter whose ID you already hold. Newsletter IDs have to come from Campaign Manager rather than from discovery.
status,limitandoffsetare documented on the sibling/subscribers/{profile-id}/subscriptionsroute but not on this one; they are passed through for servers that honour them, andprofileIds()re-filters on status client-side so an ignored filter cannot cause an unsubscribed contact to be mailed.
Mailings
Upaknee sends against a trigger — a message template configured in Campaign Manager. The API does not author content: it submits the recipients to mail and the merge-tag values to interpolate into the template. Anything variable in the message (subject line, body HTML) must be exposed as a merge tag on the trigger.
// Same content to many recipients — POST /triggers/{trigger-name}/mailings $client->mailings()->submitToProfiles( 'newsletter-broadcast', $profileIds, ['subject' => 'March update', 'body' => '<p>Hello!</p>'], ); // Schedule it instead of sending now $client->mailings()->submitToProfiles( 'newsletter-broadcast', $profileIds, ['subject' => 'March update', 'body' => '<p>Hello!</p>'], '2026-09-01T22:15:00Z', ); // Per-recipient merge data $client->mailings()->submit('newsletter-broadcast', [ ['profile-id' => 'A', 'data' => ['first_name' => 'Ada']], ['profile-id' => 'B', 'data' => ['first_name' => 'Grace']], ]);
Merge-tag values named in the serializer's CDATA list (body by default) are emitted as CDATA, so
HTML survives intact. Pass a different list to XmlSerializer if your trigger names its HTML tag
something else:
use Upanupstudios\Upaknee\Php\Client\XmlSerializer; $client = new Upaknee($transport, new XmlSerializer(['body', 'html_content']));
$scheduledFor accepts the three formats the API documents:
| Format | Example |
|---|---|
TZ (always UTC) |
2026-09-01T22:15:00Z |
| GMT offset | 2026-09-01 18:15:00 -0400 |
| Timezone abbreviation | 2026-09-01 17:15:00 EST |
Bring your own HTTP client
For full control (custom middleware, timeouts, testing), construct the transport yourself:
use GuzzleHttp\Client as GuzzleClient; use Nyholm\Psr7\Factory\Psr17Factory; use Upanupstudios\Upaknee\Php\Client\Config; use Upanupstudios\Upaknee\Php\Client\HttpClient; use Upanupstudios\Upaknee\Php\Client\Upaknee; $factory = new Psr17Factory(); $transport = new HttpClient( new GuzzleClient(), $factory, // PSR-17 request factory $factory, // PSR-17 stream factory new Config('YOUR_API_TOKEN', 'OPTIONAL_PASSWORD'), ); $client = new Upaknee($transport);
Error handling
Successful responses are decoded from XML into associative arrays. Errors throw typed exceptions
that all implement Upanupstudios\Upaknee\Php\Client\Exception\ExceptionInterface:
| Exception | When |
|---|---|
AuthenticationException |
HTTP 401 / 403 |
NotFoundException |
HTTP 404 |
ValidationException |
HTTP 406 / 422 (e.g. duplicate or invalid email) |
ApiException |
Any other HTTP error (base class; carries the PSR-7 response) |
TransportException |
Network/transport failure (no response received) |
InvalidArgumentException |
Bad arguments before a request is sent (e.g. a mailing with no recipients) |
use Upanupstudios\Upaknee\Php\Client\Exception\ApiException; try { $subscriptions = $client->newsletters()->subscriptions('15'); } catch (ApiException $e) { echo $e->getStatusCode(); // e.g. 404 $response = $e->getResponse(); // PSR-7 ResponseInterface|null }
Tests
composer install
composer test
License
MIT