Search by

paubox / paubox-php

paubox_engineering

A PHP package for the Paubox Transactional Email API

Package info

github.com/Paubox/paubox-php

pkg:composer/paubox/paubox-php

Statistics

Installs: 7 504

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 5


README

Paubox

Paubox PHP

NEW: Version 2 of the Paubox Email API SDK for PHP is available to beta test now. It includes code for newer features like bulk message sending, dynamic templates, and more. We will be deprecating the old in the near future.

This is the official PHP wrapper for the Paubox Email API.

The Paubox Email API allows your application to send secure, HIPAA compliant email via Paubox and track deliveries and opens. The API wrapper also allows you to construct and send messages.

Table of Contents

Installation

Using composer:

$ composer require paubox/paubox-php

Getting Paubox API Credentials

You will need to have a Paubox account. You can sign up here.

Once you have an account, follow the instructions on the Rest API dashboard to verify domain ownership and generate API credentials.

Configuring API Credentials

Include your API credentials in your environment file.

$ echo "export PAUBOX_API_KEY='YOUR_API_KEY'" > .env
$ source .env
$ echo ".env" >> .gitignore

Usage

To send email, prepare a Message object and call the sendMessage method of Paubox.

Sending messages

<?php
require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

$paubox = new Paubox\Paubox();

$message = new Paubox\Mail\Message();
$content = new Paubox\Mail\Content();
$content->setPlainText("Hello World");

$header = new Paubox\Mail\Header();
$header->setSubject("Testing!");
$header->setFrom("sender@domain.com");

$recipients = array();
array_push($recipients,'recipient@example.com');

$message->setHeader($header);
$message->setContent($content);
$message->setRecipients($recipients);

$sendMessageResponse = new Paubox\Mail\SendMessageResponse();
$sendMessageResponse = $paubox->sendMessage($message);
print_r($sendMessageResponse);

Allowing non-TLS message delivery

If you want to send non-PHI mail that does not need to be HIPAA-compliant, you can allow the message delivery to take place even if a TLS connection is unavailable.

This means the message will not be converted into a secure portal message when a non-TLS connection is encountered. To allow a non-TLS message delivery, call the setAllowNonTLS(true) method on the message object.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

$paubox = new Paubox\Paubox();

$message = new Paubox\Mail\Message();
$content = new Paubox\Mail\Content();
$content->setPlainText("Hello World");

$header = new Paubox\Mail\Header();
$header->setSubject("Testing!");
$header->setFrom("sender@domain.com");

$recipients = array();
array_push($recipients,'recipient@example.com');

$message->setHeader($header);
$message->setContent($content);
$message->setRecipients($recipients);
$message->setAllowNonTLS(true);

$sendMessageResponse = new Paubox\Mail\SendMessageResponse();
$sendMessageResponse = $paubox->sendMessage($message);
print_r($sendMessageResponse);

Forcing Secure Notifications

Paubox Secure Notifications allow an extra layer of security, especially when coupled with an organization's requirement for message recipients to use 2-factor authentication to read messages (this setting is available to org administrators in the Paubox Admin Panel).

Instead of receiving an email with the message contents, the recipient will receive a notification email that they have a new message in Paubox.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

$paubox = new Paubox\Paubox();

$message = new Paubox\Mail\Message();
$content = new Paubox\Mail\Content();
$content->setPlainText("Hello World");

$header = new Paubox\Mail\Header();
$header->setSubject("Testing!");
$header->setFrom("sender@domain.com");

$recipients = array();
array_push($recipients,'recipient@example.com');

$message->setHeader($header);
$message->setContent($content);
$message->setRecipients($recipients);
$message->setForceSecureNotification("true");

$sendMessageResponse = new Paubox\Mail\SendMessageResponse();
$sendMessageResponse = $paubox->sendMessage($message);
print_r($sendMessageResponse);

Adding Attachments and Additional Headers

<?php
require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

$paubox = new Paubox\Paubox();

$message = new Paubox\Mail\Message();
$content = new Paubox\Mail\Content();
$content->setPlainText("Hello World");
$content->setHtmlText("<html><head></head><body>Hello World</body></html>");

$header = new Paubox\Mail\Header();
$header->setSubject("Testing!");
$header->setFrom("sender@domain.com");
$header->setReplyTo("reply_to@domain.com");

$firstAttachment = new Paubox\Mail\Attachment();
$firstAttachment->setFileName("hello_world.txt");
$firstAttachment->setContentType("text/plain");
$firstAttachment->setContent("SGVsbG8gV29ybGQh\n");

$secondAttachment = new Paubox\Mail\Attachment();
$secondAttachment->setFileName("hello_world2.txt");
$secondAttachment->setContentType("text/plain");
$secondAttachment->setContent("SGVsbG8gV29ybGQh\n");

$attachments = array();
array_push($attachments,$firstAttachment);
array_push($attachments,$secondAttachment);

$recipients = array();
array_push($recipients,'recipient@example.com');

$bcc = array();
array_push($bcc, 'recipient2@example.com');

$cc = array();
array_push($cc, 'recipientcc@example.com');

$message->setHeader($header);
$message->setContent($content);
$message->setAttachments($attachments);
$message->setRecipients($recipients);
$message->setBcc($bcc);

$sendMessageResponse = new Paubox\Mail\SendMessageResponse();
$sendMessageResponse = $paubox->sendMessage($message);
print_r($sendMessageResponse);

Checking Email Dispositions

The SOURCE_TRACKING_ID of a message is returned in the response of the sendMessage method. To check the status for any email, use its source tracking id and call the getEmailDisposition method of Paubox:

<?php
require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
$dotenv->load();

$paubox = new Paubox\Paubox();

$resp = $paubox->getEmailDisposition('SOURCE_TRACKING_ID');
print_r($resp);

Paubox Forms

PauboxForms provides access to the Paubox Forms API. The public endpoints — getting a form definition and submitting a form response — are unauthenticated and need no credentials. The form-management endpoints (listing, creating, updating, archiving, and copying forms, plus retrieving submissions, stats, CSVs, and PDFs) require a scoped API key — see Scoped API keys below.

Getting a form

Retrieve the full definition of a form (HTML, JSON schema, CSS, and metadata) by its UUID.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms();

$form = $forms->getForm('YOUR-FORM-UUID');
echo $form->title;       // "Patient Intake Form"
echo $form->form_html;   // "<form>...</form>"
print_r($form->form_json);

Submitting a form

Build a FormSubmission with form_data matching the form's field schema and call submitForm. Returns true on success; throws an exception on failure.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms();

$submission = new Paubox\Forms\FormSubmission();
$submission->setFormData([
    'first_name' => 'Jane',
    'last_name'  => 'Smith',
    'email'      => 'jane@example.com',
]);

$result = $forms->submitForm('YOUR-FORM-UUID', $submission);
// $result === true on success

Submitting a form with file attachments

Attachments must be base64-encoded. Maximum total request size is 250 MB.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms();

$attachment = new Paubox\Forms\FormAttachment();
$attachment->setName('consent.pdf');
$attachment->setContent(base64_encode(file_get_contents('/path/to/consent.pdf')));

$submission = new Paubox\Forms\FormSubmission();
$submission->setFormData(['first_name' => 'Jane', 'last_name' => 'Smith']);
$submission->setAttachments([$attachment]);

$result = $forms->submitForm('YOUR-FORM-UUID', $submission);

Scoped API keys

All of the form-management methods below require a Paubox scoped API key with the forms scope. Generate one from your Paubox dashboard, then either pass it to the PauboxForms constructor or set it in the PAUBOX_FORMS_API_KEY environment variable. The SDK sends it as an Authorization: Bearer header.

Important: PAUBOX_FORMS_API_KEY is a different credential from the transactional Email API key (PAUBOX_API_KEY). Do not reuse the same value for both — although both are sent as Authorization: Bearer headers to the same host, they are scoped to different APIs: the email client sends PAUBOX_API_KEY to https://api.paubox.com/v1/email, while PauboxForms sends PAUBOX_FORMS_API_KEY to https://api.paubox.com/v1/forms. Mixing them misdelivers a credential to the wrong endpoint. Configure both independently:

echo "export PAUBOX_API_KEY='YOUR_EMAIL_API_KEY'"          >> .env
echo "export PAUBOX_FORMS_API_KEY='YOUR_FORMS_SCOPED_KEY'" >> .env
<?php
require_once __DIR__ . '/vendor/autoload.php';

// Option 1: pass the key directly
$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

// Option 2: use the PAUBOX_FORMS_API_KEY environment variable
$forms = new Paubox\PauboxForms();

Optionally, override the target endpoint with a constructor argument or the PAUBOX_FORMS_BASE_URL environment variable. Defaults to https://api.paubox.com/v1/forms:

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY', 'https://staging-forms.example.com');

If no key is configured, calling a management method throws a Paubox\Forms\PauboxFormsException. The public getForm and submitForm methods never require a key.

Error handling

All Forms methods throw Paubox\Forms\PauboxFormsException on failure. The exception's message contains the operation and HTTP status only — the raw response body is not included in the message (form submission bodies can contain PHI). Read getStatusCode(), getUrl(), and getResponseBody() explicitly if you need the details:

try {
    $forms->updateForm($formId, ['title' => $newTitle]);
} catch (Paubox\Forms\PauboxFormsException $e) {
    error_log('Forms update failed: ' . $e->getMessage());
    // Read the body explicitly — do NOT log $e->getMessage() alone if
    // the caller might echo the exception unfiltered.
    if ($e->getStatusCode() === 401) {
        // handle unauthorized (missing, invalid, or wrong-scope key)
    }
}

Requirements

  • PHP 8.1 or newer (older versions cannot install because of an unrelated advisory on the vendored HTTP client).
  • The package pins its HTTP dependency (nategood/httpful ^1.0) and enables strict TLS certificate verification.

Getting a form by ID

Retrieve a form through the authenticated management endpoint. Unlike the public getForm, this also returns inactive and archived forms.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$form = $forms->getFormById('YOUR-FORM-UUID');
echo $form->title;
echo $form->active;

Listing forms

List your forms with optional filtering, searching, ordering, and pagination. Include customer_id (your Paubox customer ID) — the API rejects list requests that don't name the customer the key belongs to. Booleans are handled for you — pass real true/false values.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$result = $forms->listForms([
    'customer_id' => 12345,        // your Paubox customer ID
    'search'   => 'intake',        // match against form titles
    'archived' => false,
    'active'   => true,
    'order'    => 'desc',          // "asc" or "desc"
    'order_by' => 'updated_at',    // "title", "updated_at", "submission_count", or "created_at"
    'page'     => 1,               // 1-based
    'items'    => 25,              // max 100
]);

foreach ($result->results as $form) {
    echo $form->title . "\n";
}
print_r($result->page_info); // count, pages, page, items

Creating a form

Build a Forms\Form and call createForm. title, formJson, and customerId are required; the other setters are optional. Returns the new form's UUID.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$form = new Paubox\Forms\Form();
$form->setTitle('Patient Intake Form');
$form->setFormJson(['fields' => [['name' => 'first_name', 'type' => 'text']]]);
$form->setCustomerId(12345);
$form->setDescription('Collects new patient information');
$form->setRecipient('intake@yourclinic.com');
$form->setActive(true);

$newFormId = $forms->createForm($form);
echo $newFormId; // "NEW-FORM-UUID"

Updating a form

Partial update — pass an associative array with only the attributes you want to change. Allowed keys: title, description, form_json, vanity_url, recipient, active, subscription_list_id. Omitted keys are left unchanged.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$response = $forms->updateForm('YOUR-FORM-UUID', [
    'title'  => 'Patient Intake Form (v2)',
    'active' => false,
]);
echo $response->detail; // "Form updated successfully"

Archiving and unarchiving a form

Both return true on success and throw an exception on failure.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$forms->archiveForm('YOUR-FORM-UUID');
$forms->unarchiveForm('YOUR-FORM-UUID');

Copying a form

Duplicate an existing form under a new title. Returns the full new form object.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$newForm = $forms->copyForm('SOURCE-FORM-UUID', 'Patient Intake Form (Copy)');
echo $newForm->id;
echo $newForm->title;

Form statistics

Get aggregate stats for your forms. customer_id is optional and defaults to the customer associated with your API key.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$stats = $forms->getFormStats();
// or: $stats = $forms->getFormStats(['customer_id' => 12345]);

echo $stats->active_form_count;
echo $stats->total_submission_count;
echo $stats->submissions_last_7_days;

Listing submissions

List the submissions for a form with optional ordering and pagination.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$result = $forms->listSubmissions('YOUR-FORM-UUID', [
    'order'    => 'desc',          // "asc" or "desc"
    'order_by' => 'created_at',    // "submitter_email" or "created_at"
    'page'     => 1,
    'items'    => 25,              // max 100
]);

foreach ($result->data as $submission) {
    print_r($submission);
}
echo $result->total;

Downloading submissions as CSV

getSubmissionsCsv returns every submission for a form as a CSV string; getSubmissionCsv returns a single submission. Save the result to a file or process it directly.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

// All submissions for a form
$csv = $forms->getSubmissionsCsv('YOUR-FORM-UUID');
file_put_contents('submissions.csv', $csv);

// A single submission
$csv = $forms->getSubmissionCsv('YOUR-FORM-UUID', 'SUBMISSION-ID');
file_put_contents('submission.csv', $csv);

Downloading a submission as PDF

Returns the PDF as a binary string — write it straight to a file.

<?php
require_once __DIR__ . '/vendor/autoload.php';

$forms = new Paubox\PauboxForms('YOUR-SCOPED-API-KEY');

$pdf = $forms->getSubmissionPdf('YOUR-FORM-UUID', 'SUBMISSION-ID');
file_put_contents('submission.pdf', $pdf);

Webhooks

A webhook endpoint is a URL you own that Paubox notifies when something happens. PauboxWebhooks manages those subscriptions: which URL to notify, and for which events.

Construct it with a scoped API key, sent as Authorization: Bearer <key>. The key can also come from the PAUBOX_WEBHOOKS_API_KEY environment variable:

<?php
require_once __DIR__ . '/vendor/autoload.php';

$webhooks = new Paubox\PauboxWebhooks('YOUR-SCOPED-API-KEY');

Which events a key may subscribe to follows from its scopes. The SDK does not inspect them — the service refuses an event the key isn't scoped for with 403, and an unrecognised event with 422.

Available events

Event Fires when
forms.submission.created A form submission is received
api_mail_log_delivered An Email API message is delivered
api_mail_log_opened A delivered message is opened
api_mail_log_temporary_failure Delivery fails temporarily
api_mail_log_permanent_failure Delivery fails permanently
email.inbound.received Inbound mail is received

The four Email API events keep their legacy api_mail_log_ names, so a receiver written against the old Email API webhooks keeps working unchanged. Note these are the names on the wire: delivered or failed on their own are rejected as unknown.

The SDK does not check these names. The catalog lives in the service, so an event added later works here without upgrading the SDK.

Creating a webhook endpoint

$endpoint = $webhooks->createWebhookEndpoint([
    'target_url' => 'https://example.com/paubox-webhook',
    'events'     => ['forms.submission.created'],
]);

echo $endpoint->id;              // UUID
echo $endpoint->signing_secret;  // whsec_... — store this now

signing_secret is returned once, here. It is not on getWebhookEndpoint or listWebhookEndpoints, and there is no way to read it back — recovering from a lost secret means deleting the endpoint and creating a new one. Use it to verify that deliveries really came from Paubox.

Listing webhook endpoints

$result = $webhooks->listWebhookEndpoints();

foreach ($result->data as $endpoint) {
    echo "{$endpoint->id} {$endpoint->target_url} {$endpoint->status}\n";
}

echo $result->page_info->count;  // total matching, not the length of this page

Pass page and items to paginate:

$page2 = $webhooks->listWebhookEndpoints(['page' => 2, 'items' => 25]);

Getting a webhook endpoint

$endpoint = $webhooks->getWebhookEndpoint('2ec66c21-bf48-48eb-8d28-f80b2d6b77c7');

Updating a webhook endpoint

Only the keys you pass are sent, so changing the status leaves the URL and events alone:

$endpoint = $webhooks->updateWebhookEndpoint($id, ['status' => 'disabled']);

Pausing deliveries without losing the subscription is what disabled is for; set active to resume. Passing events replaces the list rather than adding to it.

Deleting a webhook endpoint

$webhooks->deleteWebhookEndpoint($id);

Returns true — the service answers 204 No Content. Deleting stops every event on that endpoint, and the signing secret goes with it.

Webhook error handling

A non-2xx response throws Paubox\Webhooks\PauboxWebhooksException. The service's own message is promoted onto the exception message, since a bare status says nothing about which field was wrong; the raw body is on getResponseBody():

use Paubox\Webhooks\PauboxWebhooksException;

try {
    $webhooks->createWebhookEndpoint($params);
} catch (PauboxWebhooksException $e) {
    echo $e->getStatusCode();    // 422
    echo $e->getMessage();       // createWebhookEndpoint failed: HTTP 422: target_url: ...
    echo $e->getResponseBody();  // raw response
}

Two cases worth handling explicitly: subscribing a target_url that already has an endpoint comes back as 422, and an id that doesn't belong to your account is a 404 rather than a 403. An id that isn't a UUID is rejected locally, before any request is made.

Running the tests

The default test suite is offline and read-only. Requires no credentials, no fixtures, no network:

$ vendor/bin/phpunit src/tests/PauboxFormsTest.php

Tests that hit the network or write to a real customer account are separated into groups and excluded by default:

  • network — read-only tests that resolve a real API endpoint (e.g. the public getForm 404 path).
  • mutating — creates, copies, archives, updates, or submits against a real customer. Restores state where possible (try/finally), but should never run against a production customer without explicit intent.

To run them:

$ export PAUBOX_FORMS_API_KEY='your-scoped-forms-key'
$ export PAUBOX_FORMS_BASE_URL='https://your-staging-endpoint'   # optional
$ export QA_TEST_FORM_UUID='...'         # a form on the target customer
$ export QA_TEST_SUBMISSION_UUID='...'   # a submission on that form
$ export QA_TEST_CUSTOMER_ID='...'       # numeric customer id owning them
$ vendor/bin/phpunit --group mutating src/tests/PauboxFormsTest.php

⚠️ The mutating group writes to whatever host PAUBOX_FORMS_BASE_URL points at (defaults to production). Every test attempts to restore state in a finally block, but a crash between an archive and a restore leaves the form archived — verify the test customer is a QA account before running.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/paubox/paubox-php.

License

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Copyright

Copyright © 2021, Paubox, Inc.

💬 Community & support

Questions, ideas, or want to share what you built? Join the Paubox Community — the single home for discussions across every Paubox SDK and API.

🔐 Found a security issue? Email devops@paubox.com — please don't post it publicly.