onebytesolutions / currera
PHP client for Currera transactional email, SMS, and subscriber APIs.
Requires
- php: ^8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-10 01:25:16 UTC
README
PHP client for the Currera transactional email, transactional SMS, and subscriber APIs. Uses fluent message builders and convenient response methods while sending Currera's own API payloads.
Requires PHP 8.1 or newer with the cURL and JSON extensions.
Installation
After this repository is published and registered on Packagist:
composer require onebytesolutions/currera
Before Packagist registration, add the GitHub repository to your application's composer.json and require the branch you publish (this example assumes main):
{
"repositories": [
{"type": "vcs", "url": "https://github.com/onebytesolutions/currera"}
],
"require": {
"onebytesolutions/currera": "dev-main"
}
}
For local development, use a Composer path repository pointing to this directory.
Configuration
require __DIR__ . '/vendor/autoload.php';
$currera = new \Currera\Client(
getenv('CURRERA_API_KEY'),
'https://currera.example.com',
timeout: 30,
connectTimeout: 10
);
Use your installation URL, including its subdirectory if applicable, without the /api suffix. The client appends the endpoint path. API keys use bearer authentication. Use HTTPS for deployed installations and keep keys outside source control.
Assign the service account a role with only the API permissions it needs:
| Operation | Permission |
|---|---|
| Send transactional email | transactional_email.send |
| Send transactional SMS | transactional_sms.send |
| Add a subscriber/list membership | subscribers.create |
| Update subscriber profile fields | subscribers.edit |
These permissions require Currera migration 017_api_operation_permissions.php. Existing role access is preserved during migration; review service-account roles to remove broader permissions when they are no longer needed.
Send email
$mail = (new \Currera\Mail())
->setTemplateId('YOUR_EMAIL_TEMPLATE_ID')
->setFrom('receipts@example.com', 'Receipts')
->setReplyTo('support@example.com')
->setSubject('Order {{ORDER_NUMBER}}')
->addTo('customer@example.com', 'Alex', ['FIRST_NAME' => 'Alex'])
->addDynamicTemplateData('ORDER_NUMBER', 'A-1007')
->setTracking(true, false);
$response = $currera->send($mail);
Templates must be active email templates. IDs may be the template UUID, an existing imported template identifier, or a numeric Currera template ID. The subject defaults to the saved template subject when omitted. The sender defaults to the selected provider's first verified sender. Explicit senders must be individually verified or belong to an exact verified domain on that provider. For example, verifying datescapes.com allows dev@datescapes.com without a separate mailbox profile. Subdomains require their own verification.
Call addTo repeatedly for multiple recipients (at most 100 per request). Recipient placeholders override shared dynamic template data. Email placeholders are escaped by Currera for HTML content.
Send raw HTML or plain text
Use the same client method without selecting a template:
$mail = (new \Currera\Mail())
->setFrom('receipts@example.com', 'Receipts')
->setSubject('Order {{ORDER_NUMBER}}')
->setHtml('<p>Hello {{NAME}}, your order is {{ORDER_NUMBER}}.</p>')
->setText('Hello {{NAME}}, your order is {{ORDER_NUMBER}}.')
->addTo('customer@example.com', 'Alex')
->addDynamicTemplateData('ORDER_NUMBER', 'A-1007');
$response = $currera->send($mail);
Requires the updated Currera API and transactional_email.send permission. Provide a subject and at least one non-empty body using setHtml() or setText(). Both can be supplied for multipart email; neither is generated automatically. Do not combine raw bodies with setTemplateId(); the API rejects mixed requests with 422 rather than silently selecting one source.
Raw bodies support shared and recipient placeholders. HTML placeholder values are escaped; the HTML markup you supply becomes the email body. Verified sender requirements, suppressions, optional tracking, and recipient results are the same as for template sends. A raw send returns data.template_id as null.
Raw arrays also work: send a subject and html and/or text instead of template_id.
Send SMS
$sms = (new \Currera\Sms())
->setTemplateId('YOUR_SMS_TEMPLATE_ID')
->addTo('+12025550123', 'Alex')
->addDynamicTemplateData('ORDER_NUMBER', 'A-1007');
$response = $currera->sendSms($sms);
Templates must be active SMS templates, identified by UUID or numeric ID. Recipients must already be active Currera contacts with recorded SMS opt-in and E.164 phone numbers. Sending does not create consent. SMS supports at most 100 recipients per request.
Optionally call setFrom('+12025550999') with a verified sending number on the selected SMS provider. Otherwise Currera chooses its first verified sender. SMS does not support open or click tracking.
Provider selection
Both builders support setProviderId(2) or setProvider('driver-code'). The most recent provider setter replaces the previous selection. If omitted, Currera chooses the default active provider for the channel.
Email drivers include amazon-ses, smtp, and log. SMS drivers include twilio and log; actual availability depends on your installation.
Subscribe a contact
$response = $currera->subscribe(
'a7c93e2b8f0146d5',
'customer@example.com',
[
'first_name' => 'Alex',
'last_name' => 'Smith',
'bypass_double_opt_in' => false,
]
);
Use the displayed 16-character List ID, not a numeric database ID. Names apply to newly created contacts. Double opt-in is respected by default. Explicitly setting bypass_double_opt_in to the boolean true activates membership without a confirmation email, subject to server suppression and unsubscribe rules.
Unsubscribe by email
$client->unsubscribe($listId, $email); $client->unsubscribeAll($email);
Both methods call POST /api/unsubscribe with an email and either the displayed
16-character List ID (list_id) or an explicit all_lists: true. The server
must support this endpoint and the API key needs subscribers.unsubscribe.
The response includes data.unsubscribed_count, which is zero when no
memberships need changing. Unsubscribing from all applies to current email list
memberships and preserves the contact and SMS consent.
Contact and list management
$client->getContact($email); $client->listLists(); $client->upsertContacts([ ['email' => $email, 'first_name' => 'Sam'], ], [$listId]);
Contact lookup returns the current memberships and their statuses. List
discovery returns the public List ID used by subscription operations. Pass
data.next_after_id as the first argument to request the next page.
Upsert accepts up to 100 contacts and 10 optional list IDs per call. Names and
existing custom-field slugs may be provided. List assignment observes double
opt-in, existing opt-outs, and suppressions. Inspect each entry in
data.contacts for its membership results or an error for that contact.
Read operations require lists.view. Upsert requires both
subscribers.create and subscribers.edit.
Email suppressions
$client->listSuppressions(); $client->getSuppression($email); $client->addSuppression($email); $client->removeSuppression($suppressionId); $group = $client->saveSuppressionGroup('Product updates')->json()['data']; $client->setGroupPreference($group['id'], $email, true); $client->getGroupPreferences($email);
Address-level suppression blocks future email and unsubscribes existing
memberships. Removal takes a suppression record ID and leaves list opt-outs in
place. Other suppression records for the same email can still block sending.
Groups are independent of mailing lists and apply when sendMail() supplies
asm.group_id. Administrators can manage groups and opt-outs under
Email Suppressions → Suppression groups. Recipients can update their own
group opt-outs on the signed Manage subscriptions page.
Read and write operations require suppressions.view and suppressions.edit,
respectively.
Transactional email with personalizations
$client->sendMail([ 'from' => ['email' => $verifiedSender], 'subject' => 'Your receipt', 'personalizations' => [ ['to' => [['email' => $email]]], ], 'content' => [['type' => 'text/plain', 'value' => 'Thanks for your order.']], ]);
sendMail() uses Currera's POST /api/mail-send and response envelope. It
accepts up to 100 total recipients. Each To address gets a separate message.
Use one To address per personalization when adding CC or BCC. Support for
CC, BCC, attachments, categories, and custom arguments depends on the active
delivery provider. Any suppressed address in a personalization blocks that message.
Use local Currera template IDs and Currera's scalar placeholder syntax.
Set mail_settings.sandbox_mode.enable to validate without sending or queuing.
Set top-level send_at to a Unix timestamp up to 72 hours ahead to queue a
send. Pass data.scheduled_id to getScheduledMail() to read its status.
Currera cron dispatches scheduled requests with current suppression rules and
template content. An interrupted send can have an unknown outcome.
Both operations require transactional_email.send. The server must have the
scheduled mail and suppression group migrations installed.
Responses and errors
Successful requests return a Response with statusCode(), body(), headers(), and json(). API failures throw Currera\Exception\ApiException:
try {
$response = $currera->send($mail);
$result = $response->json();
} catch (\Currera\Exception\ApiException $exception) {
echo $exception->getMessage();
echo $exception->statusCode(); // Also available through getCode().
echo $exception->errorCode(); // For example: invalid_request.
$response = $exception->response();
// Inspect response()->json()['data']['messages'] for send outcomes.
} catch (\Exception $exception) {
// Includes transport, configuration, and request JSON encoding errors.
echo $exception->getMessage();
}
All non-2xx HTTP responses throw, including redirects, authentication failures, validation failures, and server errors. Explicit API errors and failed recipients also throw, even when the HTTP status is 202. Malformed or non-JSON responses throw ApiException; their original body is available through response()->body(). A decoding failure is retained as getPrevious().
A partial send can accept some recipients and reject others. Its exception retains data.messages in the response. Do not resend to accepted recipients. Provider acceptance does not confirm final delivery. API responses use partial_send_failure for mixed outcomes and send_failed when none were accepted. Other unsuccessful HTTP responses without a structured error use http_error; invalid successful responses use invalid_response.
Subscriptions return 201 when created or 200 for an existing subscription. Transport failures throw RuntimeException. Invalid configuration throws InvalidArgumentException. Request JSON encoding failures throw JsonException. ApiException extends RuntimeException, so existing RuntimeException handlers also catch API failures. Catch Exception to handle all these exception types.
This changes earlier client behavior that returned HTTP error Response objects. Update integrations to catch exceptions instead of relying on status checks after a failed call. The API now emits top-level data/error fields without page layout wrappers, including invalid-key responses.
The client verifies TLS certificates, does not follow redirects, and never retries automatically. A timeout may occur after a provider accepted a message; repeating the request can duplicate a send.
Raw payloads
send() and sendSms() also accept arrays for payloads supported by your Currera installation:
$response = $currera->send([
'template_id' => 'YOUR_EMAIL_TEMPLATE_ID',
'addTo' => [['email' => 'customer@example.com']],
'placeholders' => (object) ['ORDER_NUMBER' => 'A-1007'],
]);
Use JSON objects for placeholder maps, especially maps with numeric keys. Builders handle this conversion automatically. Server-side validation remains authoritative; the client does not resolve templates, providers, or permissions.
Package layout
- src/Client.php: connection configuration and endpoint requests.
- src/Mail.php: template and raw HTML/plain-text email builder.
- src/Sms.php: SMS message builder.
- src/TemplateMessage.php: shared template, provider, and recipient behavior.
- src/Response.php: HTTP status, raw headers, body, and JSON access.
- examples/: email, SMS, and subscription scripts using environment credentials.
Load vendor/autoload.php to use the package.
Release and verification
Publish this library repository as onebytesolutions/currera, then register it on Packagist and create semantic version tags (for example v1.0.0). Composer derives versions from tags; composer.json intentionally has no hard-coded version.
Before release, validate Composer metadata and autoloading and exercise the builders, HTTP errors, malformed responses, authentication, subdirectory URLs, timeouts, and mixed recipient results against a development Currera installation. Run send examples only with intended recipients and configured providers.
License
MIT. Copyright (c) 2026 Jason Bryan, One Byte Solutions, LLC. See LICENSE.
Update subscriber profile values
Requires a Currera installation with POST /api/subscriber-fields support and an API key with subscribers.edit permission.
$response = $currera->updateSubscriberFields('customer@example.com', [
'company' => 'Acme',
'loyalty_points' => 120,
'renewal_date' => '2026-12-31',
'vip' => true,
]);
Adds or replaces values on an existing subscriber using exact slugs of fields already defined in Currera. It does not create contacts or field definitions. Omitted fields remain unchanged. Null or an empty string clears an optional stored value; configured defaults may then apply. Required fields cannot be cleared. Consent and membership states remain unchanged.
Values must be scalar or null. Dates require YYYY-MM-DD; dropdown/radio fields require configured options; checkboxes accept booleans or 0/1. Unknown slugs and invalid values throw ApiException with status 422 without applying any changes. A missing subscriber throws ApiException with status 404. Success returns 200 with data.subscriber_id, data.email, and data.updated_fields.
Profile values persist separately from message placeholders. For transactional messages, supply template values with addDynamicTemplateData() or addTo() placeholders; updating a profile does not copy values into a transactional send.
Placeholder names in transactional email and SMS are matched without case sensitivity: a supplied year value replaces both {{year}} and {{YEAR}}. This also applies to subjects and per-recipient overrides. Unknown tags remain unchanged, and substituted HTML values continue to be escaped.