notideus / notideus-php
Official PHP SDK for the Notideus email API
Requires
- php: >=8.2
- ext-json: *
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^11.0
- squizlabs/php_codesniffer: ^3.10
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Official PHP SDK for the Notideus email API. MIT licensed.
Requires PHP 8.2+. Built on Guzzle — responses are plain associative arrays, so payloads from the API reference work verbatim.
Install
composer require notideus/notideus-php
Quickstart
<?php use Notideus\NotideusClient; $notideus = new NotideusClient(getenv('NOTIDEUS_API_KEY')); $email = $notideus->emails()->send([ 'from' => 'Acme Inc <noreply@acme.com>', 'to' => ['jane@example.com'], 'subject' => 'Welcome', 'html' => '<p>Hi {{name}}</p>', 'variables' => ['name' => 'Jane'], 'tags' => ['welcome'], 'idempotency_key' => 'req-123', ]);
Configuration
new NotideusClient(?string $apiKey = null, array $options = []);
| Option | Type | Default | Notes |
|---|---|---|---|
apiKey |
?string (1st constructor arg) |
null |
omit for unauthenticated resources |
base_url |
string |
https://api.notideus.io |
override for self-hosting |
timeout |
float (seconds) |
30.0 |
request timeout |
max_retries |
int |
2 |
see Retry behavior below |
handler |
callable|HandlerStack|null |
Guzzle default stack | inject a custom Guzzle handler |
The key is optional: plans() and unsubscribe() work without one. Calling
an authenticated resource without a key surfaces the API's 401 as a
NotideusException.
Resources
Params and responses use snake_case exactly like the API.
Emails
$notideus->emails()->send(['from', 'to', 'subject', 'html?', 'text?', 'template_id?', 'variables?', 'tags?', 'headers?', 'reply_to?', 'idempotency_key?']); $notideus->emails()->sendBatch([['from' => …, 'to' => …, …]]); // up to 100
WhatsApp messages
$notideus->whatsapp()->messages()->send([ 'template_id' => 'tpl-1', 'to' => ['+33612345678'], 'parameters' => ['body:1' => 'Jane'], ]); $notideus->whatsapp()->messages()->sendBatch([…]);
send returns one entry per recipient: [['id' => …, 'to' => …, 'status' => …]].
Contacts
$notideus->contacts()->create(['email', 'properties?', 'topic_ids?']); $notideus->contacts()->get('jane@example.com'); $notideus->contacts()->update('jane@example.com', ['properties' => ['plan' => 'pro']]); $notideus->contacts()->delete('jane@example.com'); $notideus->contacts()->unsubscribe('jane@example.com'); $notideus->contacts()->resubscribe('jane@example.com'); $notideus->contacts()->bulk([['email', 'properties?'], …]); // up to 1000 $page = $notideus->contacts()->list(['limit' => 100, 'status' => 'subscribed']); if (isset($page['next_cursor'])) { $next = $notideus->contacts()->list(['cursor' => $page['next_cursor']]); }
Hosted unsubscribe (token-based, unauthenticated)
$info = $notideus->unsubscribe()->info($token); $notideus->unsubscribe()->unsubscribe($token); // whole contact $notideus->unsubscribe()->unsubscribe($token, [$topicId]); // specific topics $notideus->unsubscribe()->preferences($token, [$topicId]); // desired subscribed set
Plans (unauthenticated)
$catalog = $notideus->plans()->list('fr'); // locale optional
Error handling
Request-level failures throw Notideus\NotideusException:
use Notideus\NotideusException; try { $notideus->emails()->send([…]); } catch (NotideusException $e) { if ($e->getErrorCode() === 'from_domain_not_verified') { … } if ($e->getErrorCode() === 'rate_limited') { $e->getRetryAfter(); // seconds to wait } $e->getStatus(); // HTTP status }
Batch sends never throw per item — inspect the results instead:
$results = $notideus->emails()->sendBatch([…]); foreach ($results as $item) { if (isset($item['error'])) { error_log($item['index'] . ' ' . $item['error']['code'] . ' ' . $item['error']['message']); } else { error_log($item['index'] . ' ' . $item['id']); } }
A whole-batch failure (401, 402 quota_exceeded, 400 batch_too_large)
still throws.
Retry behavior
The SDK retries network errors, 5xx and 429 (honoring Retry-After) with
exponential backoff — but only idempotent requests: reads/writes other than
POST, and sends that include an idempotency_key (matching the API's replay
semantics, so a retried send never double-sends). Set max_retries: 0 to
disable.
Self-hosting / local development
$notideus = new NotideusClient(getenv('NOTIDEUS_API_KEY'), [ 'base_url' => 'http://localhost:8080', ]);
Development
composer install composer test # phpunit — mocked Guzzle handler, no infra needed composer lint # phpcs composer stan # phpstan analyse
Releasing
- Update
versionincomposer.jsonand merge tomain. - Create a GitHub Release with tag
v<version>— it must matchcomposer.json, thepublishworkflow verifies this and fails otherwise. - The
publishworkflow runs the full gate (composer install,lint,stan,test). There is no publish step and no secrets at all: Packagist auto-updates the package from the release tag. - One-time setup: submit the repository on packagist.org (GitHub hook or manual "Update" enables auto-sync from tags). Unlike npm, Packagist has no registry-existence prerequisite — the very first release can go straight through the workflow.