# Authentication Source: https://docs.paubox.com/cli/authentication Store and manage Paubox API credentials with the CLI. The CLI authenticates against authenticated Paubox endpoints (such as the [Paubox Email API](/email-api)) using your API key. Run `paubox auth login` once and your API key is stored securely; you won't need to pass it on every command. Follow the [Paubox Email API Quickstart Guide](/email-api/quickstart) to get your API key. ## Commands | Command | Description | | :------------------- | :---------------------------------------------- | | `paubox auth login` | Prompt for your API key, validate, and store it | | `paubox auth logout` | Remove stored credentials | | `paubox auth status` | Show whether credentials are currently stored | ## auth login ```bash theme={null} paubox auth login ``` ``` ? Paubox API key: ******************************** ✓ API key verified and saved. ``` The CLI validates your API key against the Paubox API before saving it. If validation fails, nothing is stored and an error is shown. ## auth logout ```bash theme={null} paubox auth logout ``` ``` ✓ Credentials removed. ``` This removes the stored API key from wherever it was saved (keychain or config file). ## auth status ```bash theme={null} paubox auth status ``` ``` ✓ Logged in. API key is valid for sending domain yourclinic.com. ``` If no credentials are stored: ``` ✗ Not logged in. Run `paubox auth login` to authenticate. ``` ## Where credentials are stored | Platform | Storage location | | :------------------------ | :----------------------------------------------------- | | macOS | macOS Keychain | | Windows | Windows Credential Vault | | Linux (with libsecret) | Secret Service (GNOME Keyring / KWallet) | | Linux (without libsecret) | `~/.config/paubox/config.json` with `0600` permissions | If your system falls back to file-based storage, never commit `~/.config/paubox/config.json` to source control. Add it to your `.gitignore` if your home directory is under version control. In CI environments, set your API key via an environment variable instead of running `auth login`. The CLI reads `PAUBOX_API_KEY` if present, and it takes precedence over stored credentials. `paubox forms get` and `paubox forms submit` call public Paubox Forms endpoints and do not require `paubox auth login`. # Commands Source: https://docs.paubox.com/cli/commands Full reference for all Paubox CLI commands and global options. ## paubox send Send a single HIPAA compliant email. ```bash theme={null} paubox send --to --subject [options] ``` ### Flags | Flag | Required | Description | | :-------------------- | :-------------------------- | :------------------------------------------------------------------------------------------------ | | `--to ` | Yes | Recipient email address. Repeat for multiple recipients: `--to a@example.com --to b@example.com` | | `--subject ` | Yes | Email subject line | | `--html ` | One of `--html` or `--text` | HTML message body | | `--text ` | One of `--html` or `--text` | Plain-text message body | | `--from ` | No | Sender address. Defaults to `defaultFrom` from config if set. Must be on a verified Paubox domain | | `--attachment ` | No | Path to a file to attach. Repeat for multiple attachments | ### Example ```bash theme={null} paubox send \ --to patient@example.com \ --from provider@yourclinic.com \ --subject "Your appointment summary" \ --html "

See attached for your visit notes.

" \ --attachment ./visit-notes.pdf ``` ### Output ``` ✓ Email sent. Tracking ID: abc123-def456 ``` Add `--json` to get machine-readable output instead of the human-readable success line. Useful for capturing the tracking ID in scripts and CI pipelines. The underlying API call is documented in the [Email API · Messages](/email-api/messages) reference. *** ## paubox status Check the delivery status of a sent message. ```bash theme={null} paubox status ``` ### Example ```bash theme={null} paubox status abc123-def456 ``` ### Output ``` Recipient Status Delivered At Opened Opened At patient@example.com delivered 2026-05-24 09:14 UTC Yes 2026-05-24 09:17 UTC ``` Add `--json` to get machine-readable output. Useful for parsing status in scripts and CI pipelines. The underlying API call is documented in the [Email API · Message receipt](/email-api/message-receipt) reference. *** ## paubox config Manage CLI configuration stored in `~/.config/paubox/config.json`. ```bash theme={null} paubox config ``` ### Subcommands | Subcommand | Description | | :-------------------------------- | :-------------------------------------- | | `paubox config set ` | Set a configuration value | | `paubox config get ` | Print the current value for a key | | `paubox config list` | Print all configuration keys and values | | `paubox config reset` | Remove all configuration values | ### Supported keys | Key | Description | | :------------ | :----------------------------------------------------------------------- | | `defaultFrom` | Default sender address used when `--from` is not passed to `paubox send` | Additional keys may be added in future releases. ### Examples ```bash theme={null} # Set a default sender paubox config set defaultFrom provider@yourclinic.com # Confirm it was saved paubox config get defaultFrom # provider@yourclinic.com # List all config paubox config list # defaultFrom=provider@yourclinic.com # Remove all config paubox config reset ``` `paubox config` stores general settings only. API credentials are managed separately with `paubox auth` and stored in the OS keychain, not in `config.json`. *** ## paubox forms Fetch and submit Paubox Forms from the terminal. These commands hit the public Forms endpoints and do not require `paubox auth login`. ```bash theme={null} paubox forms [options] ``` ### Subcommands | Subcommand | Description | | :--------------------------------------- | :----------------------------------------------------- | | `paubox forms get ` | Fetch a form's metadata | | `paubox forms submit [options]` | Submit a response (and optional attachments) to a form | ### paubox forms get Fetch a form's metadata, including its title, description, and submission count. ```bash theme={null} paubox forms get ``` #### Flags | Flag | Required | Description | | :--------- | :------- | :------------------------------------------------------------------------------- | | `` | Yes | UUID of the form to fetch | | `--json` | No | Return the raw API response object as JSON instead of the human-readable summary | #### Example ```bash theme={null} paubox forms get 550e8400-e29b-41d4-a716-446655440000 ``` #### Output ``` Title: Patient intake Description: Initial visit questionnaire Active: Yes Submission count: 142 Created at: 2026-01-08 14:22 UTC Updated at: 2026-05-19 09:41 UTC ``` Add `--json` to get the raw API response object. Useful when you need the full Form payload, including `form_json`, `form_html`, and `form_css`, for rendering or validation. The underlying API call is documented in the [Forms · Get form metadata](/forms/get-form) reference. ### paubox forms submit Submit a response to a form, optionally with attachments. ```bash theme={null} paubox forms submit [options] ``` #### Flags | Flag | Required | Description | | :--------------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | | `` | Yes | UUID of the form to submit a response to | | `--data =` | One of `--data` or `--data-file` | Form field as a `key=value` pair. Repeat for multiple fields. Values may contain `=` | | `--data-file ` | One of `--data` or `--data-file` | Path to a JSON file whose top-level string values are used as `form_data`. Merged with `--data`; `--data` takes precedence on key conflicts | | `--attach ` | No | File to include as an attachment. Repeat for multiple attachments. Total request size must not exceed 250 MB | | `--json` | No | Print machine-readable JSON output instead of the human-readable success line | #### Examples Submit with `--data` fields only: ```bash theme={null} paubox forms submit 550e8400-e29b-41d4-a716-446655440000 \ --data "first_name=Jane" \ --data "last_name=Doe" \ --data "email=jane@example.com" ``` Submit from a JSON file: ```bash theme={null} paubox forms submit 550e8400-e29b-41d4-a716-446655440000 --data-file ./fields.json ``` Override individual fields from a JSON file: ```bash theme={null} paubox forms submit 550e8400-e29b-41d4-a716-446655440000 \ --data-file ./base.json \ --data "field=override" ``` Submit with `--data` fields and an attachment: ```bash theme={null} paubox forms submit 550e8400-e29b-41d4-a716-446655440000 \ --data "name=Jane" \ --attach /path/to/signed-consent.pdf ``` #### Output ``` ✓ Form submitted successfully. ``` With `--json`: ```json theme={null} { "status": "ok", "formId": "550e8400-e29b-41d4-a716-446655440000" } ``` Use `--json` when triggering submissions from CI pipelines or other automation that needs to confirm success programmatically. The underlying API call is documented in the [Forms · Submit form response](/forms/submit-form) reference. *** ## Global options These flags work with any command. | Flag | Description | | :---------------- | :------------------------------------------------------------------------------------------- | | `--json` | Output results as JSON instead of human-readable text. Useful for scripting and CI pipelines | | `-q`, `--quiet` | Suppress all non-essential output. Errors are still printed | | `-v`, `--version` | Print the installed CLI version and exit | | `--help` | Print usage information for a command and exit | ### Examples ```bash theme={null} # Check version paubox --version # Get help for a specific command paubox send --help # Quiet mode: only print errors paubox send --to a@example.com --subject "Hi" --text "Hello" --quiet ``` ### Exit codes The CLI exits with `0` on success and a non-zero code on failure. Use this in scripts and CI pipelines to detect errors: ```bash theme={null} paubox send --to patient@example.com --subject "Hi" --text "Hello" || echo "Send failed" ``` # Paubox CLI Source: https://docs.paubox.com/cli/index Send HIPAA compliant email and drive Paubox Forms from the terminal or CI pipelines using the Paubox CLI. The Paubox CLI is a command-line tool for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). Use it to send encrypted email, check delivery status, fetch and submit forms, and manage configuration directly from your terminal, shell scripts, or CI pipelines, with no HTTP client required. ```bash theme={null} npm install -g paubox-cli ``` ## What you can do * Send encrypted, HIPAA compliant email with a single command * Check delivery and open status for any sent message * Retrieve and submit Paubox Forms responses from the terminal * Store API credentials securely in the OS keychain * Use `--json` output for scripting and automation * Set a default sender address so you don't repeat `--from` on every send ## Requirements * **Node.js** ≥ 20.12.0: [download at nodejs.org](https://nodejs.org) * **Linux only**: `libsecret-1-dev` is required for OS keychain support. Without it, credentials fall back to `~/.config/paubox/config.json` with `0600` permissions. ## Get started Install, authenticate, and send your first email in under five minutes. Full install guide for macOS, Windows, and Linux. Store and manage API credentials with the CLI. Full reference for every command and flag. ## Community & support Ask usage questions in the Paubox Community. Propose features and improvements. Never post PHI, recipient addresses, or message content in public threads. Account, billing, or anything sensitive goes to [support@paubox.com](mailto:support@paubox.com). # Installation Source: https://docs.paubox.com/cli/installation Install the Paubox CLI on macOS, Windows, or Linux. ## Requirements * **Node.js** ≥ 20.12.0. Download at [nodejs.org](https://nodejs.org) or install via your preferred version manager (`nvm`, `fnm`, etc.). * **Linux only**: Install `libsecret-1-dev` before running the CLI if you want OS keychain support. See [Linux: keychain support](#linux-keychain-support) below. ## Install ```bash npm (all platforms) theme={null} npm install -g paubox-cli ``` ```bash Homebrew (macOS) theme={null} brew tap paubox/paubox brew install paubox-cli ``` ```bash winget (Windows) theme={null} winget install Paubox.CLI ``` ## Verify the installation ```bash theme={null} paubox --version ``` You should see output like `0.1.3`. If the command is not found, ensure your global npm bin directory is on your `PATH`. ## Linux: keychain support The Paubox CLI stores credentials in the OS keychain by default. On Linux, this requires the Secret Service API, which is provided by GNOME Keyring or KWallet. The underlying `keytar` library needs `libsecret-1-dev` at install time. On Debian/Ubuntu: ```bash theme={null} sudo apt install libsecret-1-dev npm install -g paubox-cli ``` On Fedora/RHEL: ```bash theme={null} sudo dnf install libsecret-devel npm install -g paubox-cli ``` **Fallback behavior:** If `libsecret` is not available, credentials are stored in `~/.config/paubox/config.json` with `0600` permissions (readable only by your user). The CLI will warn you when it falls back to file-based storage. If you share a machine or use file-based credential storage, ensure `~/.config/paubox/config.json` is excluded from any backups or source control. # Quickstart Source: https://docs.paubox.com/cli/quickstart Install the Paubox CLI, authenticate, and send your first email in under five minutes. Install the Paubox CLI globally with npm: ```bash theme={null} npm install -g paubox-cli ``` Verify the install: ```bash theme={null} paubox --version ``` For Homebrew (macOS) and Windows (winget) install options, see [Installation](/cli/installation). Run `auth login` and enter your Paubox API key when prompted: ```bash theme={null} paubox auth login ``` ``` ? Paubox API key: ******************************** ✓ API key verified and saved. ``` Your API key is stored securely in your OS keychain. To get your API key, see [Authentication](/cli/authentication). Send your first encrypted email: ```bash theme={null} paubox send \ --to patient@example.com \ --from provider@yourclinic.com \ --subject "Your appointment summary" \ --html "

Thank you for visiting us today.

" ``` On success, the CLI prints a tracking ID: ``` ✓ Email sent. Tracking ID: abc123-def456 ```
Pass the tracking ID to `paubox status`: ```bash theme={null} paubox status abc123-def456 ``` ``` Recipient Status Delivered At Opened Opened At patient@example.com delivered 2026-05-24 09:14 UTC Yes 2026-05-24 09:17 UTC ```
## Next steps * [Installation](/cli/installation): install on other platforms or verify system requirements * [Commands](/cli/commands): full reference for every command and global option # Authentication Source: https://docs.paubox.com/csharp-sdk/authentication Configure your Paubox API credentials for the C# SDK. ## Obtain your API key The Email API client requires a single credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Option 1: Constructor parameter Pass your API key directly; useful for console apps or when managing secrets via environment variables: ```csharp theme={null} var emailLib = new EmailLibrary("YOUR_API_KEY"); ``` Read from an environment variable: ```csharp theme={null} var emailLib = new EmailLibrary( Environment.GetEnvironmentVariable("PAUBOX_API_KEY") ); ``` ## Option 2: IConfiguration (recommended for ASP.NET Core) Add your API key to `appsettings.json`: ```json theme={null} { "APIKey": "YOUR_API_KEY" } ``` Then inject `IConfiguration` and pass it to the constructor: ```csharp theme={null} // In Program.cs or Startup.cs builder.Services.AddSingleton(sp => { var config = sp.GetRequiredService(); return new EmailLibrary(config); }); ``` Or instantiate directly: ```csharp theme={null} var emailLib = new EmailLibrary(configuration); ``` The SDK reads `APIKey` from the configuration and sets the `Authorization` header automatically on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Forms client `FormsLibrary` requires no credentials. Instantiate it with no arguments: ```csharp theme={null} var formsLib = new FormsLibrary(); ``` Use `IFormsLibrary` in ASP.NET Core for dependency injection and testability: ```csharp theme={null} builder.Services.AddSingleton(); ``` ## Security notes * Never hard-code credentials in source files. Use `appsettings.json` with user secrets or environment variables. * Use [.NET user secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) during development to keep credentials out of source control. # Email client Source: https://docs.paubox.com/csharp-sdk/email-client Full reference for the Paubox C# SDK Email API client: sending messages, bulk sending, delivery tracking, dynamic templates, and error handling. ## Instantiation ```csharp theme={null} // Direct API key var emailLib = new EmailLibrary("YOUR_API_KEY"); // IConfiguration (ASP.NET Core) var emailLib = new EmailLibrary(configuration); ``` See [Authentication](/csharp-sdk/authentication) for both approaches. Use `IEmailLibrary` for dependency injection. ## Building a message ```csharp theme={null} var message = new Message { Recipients = new[] { "alice@example.com" }, // required Cc = new[] { "manager@example.com" }, // optional Bcc = new[] { "audit@example.com" }, // optional Header = new Header { From = "sender@yourdomain.com", // required Subject = "Your results are ready", // required ReplyTo = "support@yourdomain.com", // optional CustomHeaders = new Dictionary // optional { { "X-Custom-Header", "value" } } }, Content = new Content { PlainText = "Plain text body.", // at least one required HtmlText = "

HTML body.

" // optional; auto base64-encoded }, Attachments = new List { attachment }, // optional AllowNonTLS = false, // optional; default false ForceSecureNotification = "true" // optional; "true" or "false" }; ``` ### Attachments ```csharp theme={null} byte[] fileBytes = File.ReadAllBytes("report.pdf"); var attachment = new Attachment { FileName = "report.pdf", ContentType = "application/pdf", Content = Convert.ToBase64String(fileBytes) }; message.Attachments = new List { attachment }; ``` ## Send a message ```csharp theme={null} SendMessageResponse response = emailLib.SendMessage(message); Console.WriteLine("Tracking ID: " + response.SourceTrackingId); ``` The `SendMessageResponse` includes: | Property | Description | | ------------------ | --------------------------------------------- | | `SourceTrackingId` | Use to check delivery status | | `Data` | Raw response data | | `Errors` | List of `Error` objects if the request failed | ## Send bulk messages Send up to 50 messages in a single API call. Each message gets its own tracking ID. ```csharp theme={null} var messages = new[] { new Message { Recipients = new[] { "alice@example.com" }, Header = new Header { From = "f@yourdomain.com", Subject = "Hi Alice" }, Content = new Content { PlainText = "Hello Alice" } }, new Message { Recipients = new[] { "bob@example.com" }, Header = new Header { From = "f@yourdomain.com", Subject = "Hi Bob" }, Content = new Content { PlainText = "Hello Bob" } } }; SendBulkMessagesResponse bulkResponse = emailLib.SendBulkMessages(messages); foreach (var msg in bulkResponse.Messages) { Console.WriteLine($"Tracking ID: {msg.SourceTrackingId}"); } ``` ## Check delivery status ```csharp theme={null} GetEmailDispositionResponse disposition = emailLib.GetEmailDisposition(response.SourceTrackingId); foreach (var delivery in disposition.Data.Message.MessageDeliveries) { Console.WriteLine($"{delivery.Recipient} → {delivery.Status.DeliveryStatus}"); } ``` Common `DeliveryStatus` values: `delivered`, `opened`, `failed`, `pending`. ## Dynamic templates Templates use [Handlebars](https://handlebarsjs.com) syntax (`{{variable_name}}`). ### Create a template ```csharp theme={null} DynamicTemplateResponse created = emailLib.CreateDynamicTemplate( templateName: "appointment-confirmation", templatePath: @"C:\templates\appointment.html" ); ``` ### List templates ```csharp theme={null} List templates = emailLib.ListDynamicTemplates(); foreach (var t in templates) Console.WriteLine($"{t.Id}: {t.Name}"); ``` ### Get a template ```csharp theme={null} GetDynamicTemplateResponse tmpl = emailLib.GetDynamicTemplate(templateId); ``` ### Update a template ```csharp theme={null} DynamicTemplateResponse updated = emailLib.UpdateDynamicTemplate( templateId: 123, templateName: "appointment-confirmation-v2", templatePath: @"C:\templates\appointment-v2.html" ); ``` ### Delete a template ```csharp theme={null} DeleteDynamicTemplateResponse deleted = emailLib.DeleteDynamicTemplate(templateId); ``` ### Send a templated message ```csharp theme={null} var templatedMsg = new TemplatedMessage { Recipients = new[] { "jane@example.com" }, Header = new Header { From = "appointments@yourclinic.com", Subject = "Your appointment" }, TemplateName = "appointment-confirmation", TemplateValues = new Dictionary { { "first_name", "Jane" }, { "date", "2024-03-15" }, { "time", "2:00 PM" } } }; SendMessageResponse response = emailLib.SendTemplatedMessage(templatedMsg); ``` ## Error handling All methods throw `SystemException` on API errors. The exception message contains the raw JSON response body: ```csharp theme={null} try { SendMessageResponse response = emailLib.SendMessage(message); } catch (SystemException ex) { // ex.Message contains the raw JSON error response Console.WriteLine("API error: " + ex.Message); // Parse if needed: // var error = JsonSerializer.Deserialize(ex.Message); } ``` # Forms client Source: https://docs.paubox.com/csharp-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the C# SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Instantiation ```csharp theme={null} var formsLib = new FormsLibrary(); ``` Use `IFormsLibrary` for dependency injection in ASP.NET Core: ```csharp theme={null} builder.Services.AddSingleton(); ``` ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```csharp theme={null} Form form = formsLib.GetForm("your-form-uuid"); Console.WriteLine("Title: " + form.Title); Console.WriteLine("HTML: " + form.FormHtml); ``` The `Form` object includes: | Property | Description | | ------------------------- | ----------------------------------------- | | `Title` | Display name of the form | | `FormJson` | Parsed field schema | | `FormHtml` | Rendered HTML for embedding | | `FormCss` | Associated stylesheet | | `Active` | Whether the form is accepting submissions | | `SubmissionCount` | Number of submissions received | | `CreatedAt` / `UpdatedAt` | Timestamps | ## Submit a form ```csharp theme={null} var formData = new Dictionary { { "first_name", "Jane" }, { "last_name", "Smith" }, { "email", "jane@example.com" } }; formsLib.SubmitForm("your-form-uuid", formData); ``` `SubmitForm` returns `void` on success and throws on failure. ### Submitting with file attachments The maximum total request size is 250 MB. ```csharp theme={null} byte[] fileBytes = File.ReadAllBytes("consent.pdf"); var attachments = new[] { new FormAttachment { Name = "consent.pdf", Content = Convert.ToBase64String(fileBytes) } }; formsLib.SubmitForm("your-form-uuid", formData, attachments); ``` The `FormAttachment` properties: | Property | Description | | --------- | ----------------------------- | | `Name` | File name including extension | | `Content` | Base64-encoded file content | ## Error handling ```csharp theme={null} try { Form form = formsLib.GetForm("your-form-uuid"); formsLib.SubmitForm("your-form-uuid", formData); } catch (ArgumentException ex) { // Null or empty formId / formData Console.WriteLine("Invalid argument: " + ex.Message); } catch (SystemException ex) { // API error (404 form not found, 400 bad request, etc.) Console.WriteLine("API error: " + ex.Message); } ``` # Paubox C# SDK Source: https://docs.paubox.com/csharp-sdk/index Send HIPAA compliant email and submit Paubox Forms from .NET applications using the official Paubox C# SDK. The Paubox C# SDK is the official .NET client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). It provides a strongly-typed interface for sending encrypted email, managing dynamic templates, tracking delivery, and submitting forms. ## Installation The SDK is distributed as a compiled DLL. Download or clone the repository, then add a reference to `lib/Paubox.Email.API.dll` in your project. **Visual Studio:** Right-click your project → Add → Reference → Browse → select `Paubox.Email.API.dll`. **`.csproj` file:** ```xml theme={null} path\to\Paubox.Email.API.dll ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Send batches of up to 50 messages in a single API call * Create, update, and delete dynamic Handlebars templates * Send templated messages with per-message variable substitution * Track delivery status and engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * .NET 8.0 or later (.NET 9.0 also supported) ## Dependency injection The SDK ships with `IEmailLibrary` and `IFormsLibrary` interfaces, making it straightforward to register the clients in an ASP.NET Core DI container and mock them in tests. ## Get started Add the DLL, configure credentials, and send your first email in minutes. Configure via constructor params or appsettings.json. Full reference for sending email, managing templates, and tracking delivery. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/csharp-sdk/quickstart Add the Paubox C# SDK and send your first HIPAA compliant email in minutes. Download or clone the SDK repository, then add `lib/Paubox.Email.API.dll` as a reference in your project. In Visual Studio: right-click your project → **Add** → **Reference** → **Browse** → select `Paubox.Email.API.dll`. Or in your `.csproj`: ```xml theme={null} lib\Paubox.Email.API.dll ``` Pass your API key directly to the constructor: ```csharp theme={null} using Paubox; var emailLib = new EmailLibrary("YOUR_API_KEY"); ``` For ASP.NET Core applications, see [Authentication](/csharp-sdk/authentication) for the `IConfiguration` approach. ```csharp theme={null} var message = new Message { Recipients = new[] { "recipient@example.com" }, Header = new Header { From = "sender@yourdomain.com", Subject = "Your first Paubox email" }, Content = new Content { PlainText = "This message was sent with the Paubox C# SDK." } }; SendMessageResponse response = emailLib.SendMessage(message); Console.WriteLine("Sent. Tracking ID: " + response.SourceTrackingId); ``` ```csharp theme={null} GetEmailDispositionResponse disposition = emailLib.GetEmailDisposition(response.SourceTrackingId); foreach (var delivery in disposition.Data.Message.MessageDeliveries) { Console.WriteLine($"{delivery.Recipient} → {delivery.Status.DeliveryStatus}"); } ``` ## Next steps * [Email client](/csharp-sdk/email-client): bulk send, attachments, dynamic templates, error handling * [Forms client](/csharp-sdk/forms-client): retrieve form schemas and submit responses # General information Source: https://docs.paubox.com/email-api Base URL, authentication, date formats, and response codes for the Paubox Email API. If you're not sure where to start, visit the [Quickstart guide](/email-api/quickstart). To set up credentials, see [Authentication](/email-api/authentication). ## Base URL `https://api.paubox.com/v1/email` ## Authorization Pass your API key as a Bearer token in every request: `Authorization: Bearer YOUR_API_KEY` The legacy format `Authorization: Token token=YOUR_API_KEY` is also accepted. Bearer is preferred. ## Date format Dates are passed as strings formatted to RFC 2822 standards e.g. "Fri, 16 Feb 2018 13:00:00 GMT" ## Standard HTTP response codes | **Status Code** | **Status Message** | | :----------------- | :----------------- | | 200 | Service Ok | | 400 | Bad Request | | 401 | Unauthorized | | 404 | Not Found | | 500, 502, 503, 504 | Server Error | ## Community & support Ask usage questions in the Paubox Community. Propose features and improvements. Never post PHI, recipient addresses, or message content in public threads. Account, billing, or anything sensitive goes to [support@paubox.com](mailto:support@paubox.com). # Attachments Source: https://docs.paubox.com/email-api/attachments Attach files to emails sent via the Paubox Email API. You can attach files to any message sent through the Email API. Attachments are added as an `attachments` array on the message, where each entry describes a single file. The total size of all attachments must not exceed **50 MB** per message. See [Limits and overage rates](/email-api/limits) for all sending limits. ## Attachment fields Each attachment is an object with three required fields: | Field | Type | Description | | :------------ | :----- | :------------------------------------------------------------------------ | | `fileName` | string | Name the recipient will see, including the extension (e.g. `report.pdf`). | | `contentType` | string | Valid MIME type for the file, e.g. `application/pdf` or `text/plain`. | | `content` | string | The file contents, Base64-encoded. | ## REST API example Add the `attachments` array to the message body. The example below attaches a small text file (`Hello World!` Base64-encoded as `SGVsbG8gV29ybGQh`): ```json theme={null} { "data": { "message": { "recipients": ["recipient@host.com"], "headers": { "subject": "Sample email with an attachment", "from": "sender@verifieddomain.com" }, "content": { "text/plain": "Hello World!" }, "attachments": [ { "fileName": "hello_world.txt", "contentType": "text/plain", "content": "SGVsbG8gV29ybGQh" } ] } } } ``` See the full [Send a message](/email-api/messages) reference for the complete request body. ## SDK examples Most Paubox SDKs accept attachments in the same `fileName` / `contentType` / `content` shape. The file content is always Base64-encoded. ```python Python theme={null} import base64 with open('report.pdf', 'rb') as f: encoded = base64.b64encode(f.read()).decode('utf-8') attachment = { 'fileName': 'report.pdf', 'contentType': 'application/pdf', 'content': encoded } # Pass attachments via the optional_headers dict optional_headers = { 'attachments': [attachment] } ``` ```javascript Node.js theme={null} const fs = require('fs'); const attachment = { fileName: 'report.pdf', contentType: 'application/pdf', content: fs.readFileSync('report.pdf').toString('base64') }; const options = { attachments: [attachment] }; ``` For language-specific details, see each SDK's email client guide — for example the [Python SDK](/python-sdk/email-client) or [Node.js SDK](/node-sdk/email-client). ## Sending from the CLI The Paubox CLI accepts files directly with the `--attachment` flag and handles the Base64 encoding for you: ```bash theme={null} paubox send \ --from sender@verifieddomain.com \ --to recipient@host.com \ --subject "Sample email with an attachment" \ --text "Hello World!" \ --attachment report.pdf ``` See the [CLI commands reference](/cli/commands) for all available flags. # Authentication Source: https://docs.paubox.com/email-api/authentication How to obtain and pass credentials to the Paubox Email API. Every request to the Paubox Email API must include your API key in the `Authorization` header. Keys are generated per domain from the [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) page. ## Find your credentials Go to [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) and click the domain you want to send from. Click **Add API Key**, give it a description, and save. Copy the key immediately; it is displayed only once. All Email API requests go to the same base URL: ``` https://api.paubox.com/v1/email ``` ## Pass credentials in requests Include the `Authorization` header with every API call: ```bash theme={null} curl --request POST \ --url https://api.paubox.com/v1/email/messages \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' ``` ### Bearer format (preferred) ``` Authorization: Bearer YOUR_API_KEY ``` ### Legacy Token format (also accepted) ``` Authorization: Token token=YOUR_API_KEY ``` Both formats are accepted. New integrations should use Bearer. ## Key rotation * Generate a new key before revoking an old one to avoid downtime. * Each domain can have multiple active keys, which is useful for rotating across services independently. * Revoke keys immediately if they are exposed or a team member with access leaves. Never commit API keys to source control. Use environment variables or a secrets manager to inject credentials at runtime. ```bash theme={null} # Good export PAUBOX_API_KEY=your_api_key curl -H "Authorization: Bearer $PAUBOX_API_KEY" ... # Bad: do not do this curl -H "Authorization: Bearer sk_live_abc123..." ... ``` # Send bulk messages Source: https://docs.paubox.com/email-api/bulk-messages openapi1 POST /bulk_messages Sends multiple messages in one request. Paubox recommends batches of 50 or fewer. Source tracking IDs are returned in the same order as the messages array. # List dynamic templates Source: https://docs.paubox.com/email-api/dynamic-templates openapi1 GET /dynamic_templates Retrieve all dynamic templates for your organization # Dynamic templates Source: https://docs.paubox.com/email-api/dynamic-templates-guide Create reusable Handlebars email templates and send personalized messages through the Paubox Email API. Dynamic templates let you store a Handlebars `.hbs` template on Paubox once and reference it by name when sending. Variable substitution, conditionals, and loops are resolved server-side before delivery, keeping your send requests small and consistent. ## Handlebars syntax Templates use [Handlebars](https://handlebarsjs.com) syntax. The most common constructs: | Syntax | Description | | :---------------------------- | :----------------------------------------------- | | `{{variable}}` | Insert the value of a variable | | `{{#if condition}}...{{/if}}` | Conditional block (truthy check) | | `{{#each items}}...{{/each}}` | Loop over an array; use `{{this}}` for each item | ## Example template Save the following as `welcome.hbs`: ```handlebars theme={null} Hello {{name}}, Thank you for signing up{{#if plan}} for the {{plan}} plan{{/if}}. {{#if items}} Your selected items: {{#each items}}- {{this}} {{/each}} {{/if}} The Paubox Team ``` ## Workflow ### 1. Upload the template Upload your `.hbs` file via `POST /dynamic_templates`. The `data[name]` value becomes the identifier you use when sending. ```bash theme={null} curl -X POST https://api.paubox.com/v1/email/dynamic_templates \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "data[name]=welcome" \ -F "data[body]=@welcome.hbs" ``` ### 2. Send a templated message Reference the template by name in `POST /templated_messages`. Pass variable values as a JSON string in `template_values`. ```bash theme={null} curl -X POST https://api.paubox.com/v1/email/templated_messages \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "template_name": "welcome", "template_values": "{\"name\":\"Jane\",\"plan\":\"Pro\",\"items\":[\"Email API\",\"HIPAA Compliance\"]}", "message": { "recipients": ["jane@example.com"], "headers": { "subject": "Welcome to Paubox", "from": "noreply@YOUR_DOMAIN.com" } } } }' ``` `template_values` must be a JSON-encoded **string**, not a JSON object. Stringify it before including it in the request body. ## Managing templates | Operation | API reference | | :-------------------- | :--------------------------------------------------------------- | | List all templates | [List dynamic templates](/email-api/dynamic-templates) | | Create a template | [Create a dynamic template](/email-api/dynamic-templates/create) | | Get a template | [Get a dynamic template](/email-api/dynamic-templates/get) | | Update a template | [Update a dynamic template](/email-api/dynamic-templates/update) | | Delete a template | [Delete a dynamic template](/email-api/dynamic-templates/delete) | | Send using a template | [Send a templated message](/email-api/templated-messages) | # Create a dynamic template Source: https://docs.paubox.com/email-api/dynamic-templates/create openapi1 POST /dynamic_templates Upload a new Handlebars template for dynamic content generation # Delete a dynamic template Source: https://docs.paubox.com/email-api/dynamic-templates/delete openapi1 DELETE /dynamic_templates/{id} Delete a specific dynamic template by ID # Get a dynamic template Source: https://docs.paubox.com/email-api/dynamic-templates/get openapi1 GET /dynamic_templates/{id} Retrieve a specific dynamic template by ID # Update a dynamic template Source: https://docs.paubox.com/email-api/dynamic-templates/update openapi1 PATCH /dynamic_templates/{id} Update an existing Handlebars template # Errors Source: https://docs.paubox.com/email-api/errors HTTP error codes returned by the Paubox Email API, with common causes and fixes. The Paubox Email API uses standard HTTP status codes. All error responses include a JSON body with a `message` field describing the problem. ## Status codes | Code | Meaning | Common cause | | :---- | :-------------------- | :------------------------------------------------------------------------------ | | `200` | OK | Request succeeded | | `400` | Bad Request | Invalid request body: see checklist below | | `401` | Unauthorized | Missing or invalid API key | | `403` | Forbidden | API key valid but lacks permission for this action | | `404` | Not Found | Resource (e.g. tracking ID) does not exist | | `422` | Unprocessable Entity | Request is well-formed but semantically invalid (e.g. unverified `from` domain) | | `429` | Too Many Requests | Rate limit exceeded; see [Limits](/email-api/limits) | | `500` | Internal Server Error | Unexpected server-side error | | `502` | Bad Gateway | Upstream connectivity issue; retry with backoff | | `503` | Service Unavailable | Temporary outage; check [status.paubox.com](https://status.paubox.com) | | `504` | Gateway Timeout | Request timed out; retry with backoff | ## 400 Bad Request: debugging checklist A `400` usually means the request body is missing a required field or contains an invalid value. Work through this list: **The most common causes of 400 Bad Request:** * **`from` domain not verified**: the sender address must belong to a domain you have verified in [Email API > Settings](https://next.paubox.com/emailapi/settings). Sending from `@gmail.com` or any unverified domain always returns a 400. * **Missing `subject`**: `data.message.headers.subject` is required. * **Empty `recipients` array**: `data.message.recipients` must contain at least one valid email address. * **Invalid base64 in attachment**: the `content` field of every attachment must be valid base64-encoded data. * **No message body**: at least one of `text/plain` or `text/html` must be present in `data.message.content`. ## 401 Unauthorized The API key is missing or malformed. ```bash theme={null} # Check: is the Authorization header present and using a valid key? curl -H "Authorization: Bearer YOUR_API_KEY" \ https://api.paubox.com/v1/email/messages ``` ## 429 Too Many Requests You have exceeded the rate limit for your plan. Back off and retry. See [Limits](/email-api/limits) for rate limit details. For high-volume sending, use the [bulk messages](/email-api/bulk-messages) endpoint (recommended max 50 per request) rather than looping over individual `/messages` calls. ## Example error response ```json theme={null} { "errors": "Sender domain not verified" } ``` # Limits and overage rates Source: https://docs.paubox.com/email-api/limits Sending limits, rate limits, and overage pricing for the Paubox Email API. ## Sending limits | Limit | Value | | :------------------------------------------ | :---- | | Max recipients per message | 100 | | Max attachment size (total per message) | 50 MB | | Bulk messages (recommended max per request) | 50 | For high-volume sends, use the [bulk messages](/email-api/bulk-messages) endpoint rather than looping over individual `/messages` calls. Paubox recommends batches of 50 or fewer messages per request. If you receive a `429 Too Many Requests` response, back off and retry. See [Errors](/email-api/errors) for retry guidance. ## What to do if you've reached your limit You will be charged a small amount per-email for each email that you send beyond the limit of your current plan. Any overage charges will appear on an invoice of the month after any over-limit sending occurs. ## Paubox Email API overage costs If you go over your plan limits, here’s a breakdown of the overage costs you’ll incur: | **Plan Information** | **Monthly Plan Limits** | **Cost Per Extra Email** | | -------------------- | ----------------------- | ------------------------ | | Free | 300 | - | | Pro | 10,000 emails | \$0.0130 | | Pro | 30,000 emails | \$0.0130 | | Pro | 50,000 emails | \$0.0100 | | Pro | 100,000 emails | \$0.0098 | | Pro | Custom | - | # Get message receipt Source: https://docs.paubox.com/email-api/message-receipt openapi1 GET /message_receipt Retrieve delivery status, open tracking, and click tracking information for a sent message # Send a message Source: https://docs.paubox.com/email-api/messages openapi1 POST /messages # Migrate from Mailgun Source: https://docs.paubox.com/email-api/migrate-from-mailgun Switch from Mailgun to the Paubox Email API for HIPAA compliant transactional email. ## Why migrate HIPAA requires a signed Business Associate Agreement (BAA) with any vendor that handles Protected Health Information (PHI) in transit, including transactional email providers. While Mailgun will sign a Business Associate Agreements (BAAs), itʻs essentially a server-side BAA only. Email in transit over the internet is explicitly excluded from protection obligations, TLS is opportunistic (not enforced), and the customer is held responsible for encrypting PHI. This is the exact gap Paubox closes, email encryption with no fallback to plaintext, regardless of what the recipient's mail server supports. Paubox is purpose-built for HIPAA compliant email and signs a BAA with every customer. While Mailgun's API differs from Paubox's in a few key ways (notably request format and authentication), the concepts map cleanly and the migration is straightforward. ## What stays the same * REST API over HTTPS * Domain authentication: SPF and DKIM records required * SMTP as an alternative to the HTTP API * Webhook-based event notifications for delivery status * Per-domain sending configuration ## Key differences | Dimension | Mailgun | Paubox Email API | | :------------------ | :----------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | | Base URL | `https://api.mailgun.net/v3/YOUR_DOMAIN/messages` | `https://api.paubox.com/v1/email/messages` | | EU region URL | `https://api.eu.mailgun.net/v3/YOUR_DOMAIN/messages` | No region selection required | | Auth method | HTTP Basic Auth (username `api`, password = API key) | Bearer token: `Authorization: Bearer $PAUBOX_API_KEY` | | Request body format | `multipart/form-data` (`-F` flags in curl) | `application/json` (JSON body) | | Request body shape | Flat fields: `from`, `to`, `subject`, `html`, `text` | `data.message` object with `headers` and `content` keys | | SMTP host | `smtp.mailgun.org` | `smtp.paubox.com` | | SMTP username | Per-domain login (e.g. `postmaster@yourdomain.com`) | `apikey` (literal) | | SMTP password | Per-domain password from Mailgun Control Panel | Paubox API key | | SMTP port | 587 or 2525 | 587 (also supports 465, 25) | | Webhook events | accepted, delivered, opened, clicked, unsubscribed, permanent\_fail, temporary\_fail, complained | delivered, opened, temporary\_failure, permanent\_failure | | Batch send | Same endpoint with multiple `to` recipients or recipient variables | `/bulk_messages` endpoint, recommended max 50 per request | | TLS enforcement | Optional | Always-on, cannot be disabled | ## Send a single email ```bash Mailgun (before) theme={null} curl -s --user 'api:$MAILGUN_API_KEY' \ https://api.mailgun.net/v3/YOUR_DOMAIN/messages \ -F from='provider@yourclinic.com' \ -F to='patient@example.com' \ -F subject='Your appointment summary' \ --form-string html='

See attached.

' ``` ```bash Paubox (after) theme={null} curl -X POST https://api.paubox.com/v1/email/messages \ -H "Authorization: Bearer $PAUBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "message": { "recipients": ["patient@example.com"], "headers": { "subject": "Your appointment summary", "from": "provider@yourclinic.com" }, "content": { "text/html": "

See attached.

" } } } }' ```
**Note:** Mailgun uses `multipart/form-data` with HTTP Basic Auth. Paubox uses JSON with a Bearer token. Both the `Content-Type` header and the request body structure need to be updated. ## SMTP configuration ```js Mailgun (before) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.mailgun.org', port: 587, auth: { user: 'postmaster@yourdomain.com', // per-domain SMTP login from Mailgun Control Panel pass: process.env.MAILGUN_SMTP_PASSWORD, }, }); ``` ```js Paubox (after) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.paubox.com', port: 587, auth: { user: 'apikey', pass: process.env.PAUBOX_API_KEY, }, }); ``` **Tip:** Mailgun SMTP credentials are per-domain and found under Sending → Domain Settings → SMTP credentials in the Mailgun Control Panel. Paubox uses the literal string `apikey` as username and your API key as password; no per-domain credential needed. ## Webhook event mapping | Mailgun event | Paubox event key | Notes | | :--------------- | :------------------------------- | :--------------------------------------------------------------------------------------------- | | `delivered` | `api_mail_log_delivered` | | | `opened` | `api_mail_log_opened` | | | `permanent_fail` | `api_mail_log_permanent_failure` | | | `temporary_fail` | `api_mail_log_temporary_failure` | | | `clicked` | No equivalent | Remove handler or no-op; poll click data via [Get message receipt](/email-api/message-receipt) | | `unsubscribed` | No equivalent | Manage unsubscribes in the Paubox dashboard | | `complained` | No equivalent | Remove handler | | `accepted` | No equivalent | Remove handler; `delivered` confirms acceptance | **Note:** Click tracking is available by polling `GET /message_receipt?sourceTrackingId=...`; it is not delivered as a push webhook event. **Note:** Mailgun webhooks are configured per domain under Sending → Webhooks in the Control Panel, or via the Webhooks API. Paubox webhooks are configured in the Paubox dashboard under Email API → Webhooks. ## Migration checklist Required before go-live. Contact Paubox to initiate the Business Associate Agreement. Add your domain on the [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) page and complete the TXT record verification. See the [Quickstart guide](/email-api/quickstart) for step-by-step instructions. From the Settings page, generate an API key. All requests use the base URL `https://api.paubox.com/v1/email`. Replace the Mailgun domain-namespaced URL and Basic Auth header with the Paubox endpoint and Bearer token. Change `Content-Type` from `multipart/form-data` to `application/json` and restructure the body to use the `data.message` shape shown above. If you use the SMTP path, update `host` to `smtp.paubox.com`, set `username` to the literal string `apikey`, and `password` to your Paubox API key. Update your webhook endpoint using the [event mapping table](#webhook-event-mapping) above. Remove handlers for `clicked`, `unsubscribed`, `complained`, and `accepted`. Paubox has a single global endpoint; `api.eu.mailgun.net` has no equivalent and should be removed. Paubox enforces TLS on every message. Any `allowNonTLS: true` or equivalent settings should be removed. Confirm delivery using the [Get message receipt](/email-api/message-receipt) endpoint with the `sourceTrackingId` returned from your test send. Replace Mailgun SPF/DKIM records with the Paubox records shown in your Settings page. Once traffic has fully moved to Paubox, revoke your Mailgun API keys and SMTP credentials. ## Next steps Full setup walkthrough from account creation to first send Configure delivery event notifications Send up to 50 messages in a single request Connect via SMTP instead of REST # Migrate from Postmark Source: https://docs.paubox.com/email-api/migrate-from-postmark Switch from Postmark to the Paubox Email API for HIPAA compliant transactional email. ## Why migrate Postmark does not sign Business Associate Agreements (BAAs). HIPAA requires a signed BAA with any vendor that handles Protected Health Information (PHI) in transit, including transactional email providers. Paubox is purpose-built for HIPAA compliant email and signs a BAA with every customer. The Postmark API uses familiar REST/JSON patterns, so the switch is straightforward for developers already comfortable with Postmark. ## What stays the same * REST API over HTTPS with JSON request bodies * API key authentication via a request header * Domain authentication: SPF and DKIM records required * SMTP as an alternative to the HTTP API * Webhook-based event notifications for delivery status ## Key differences | Dimension | Postmark | Paubox Email API | | :----------------- | :--------------------------------------------------------------- | :-------------------------------------------------------- | | Base URL | `https://api.postmarkapp.com/email` | `https://api.paubox.com/v1/email/messages` | | Auth header | `X-Postmark-Server-Token: $TOKEN` | `Authorization: Bearer $PAUBOX_API_KEY` | | Request body shape | `From`, `To`, `Subject`, `HtmlBody` flat keys | `data.message` object with `headers` and `content` keys | | SMTP host | `smtp.postmarkapp.com` | `smtp.paubox.com` | | SMTP username | Postmark server token (as username) | `apikey` (literal) | | SMTP password | Postmark server token (as password) | Paubox API key | | SMTP port | 587 or 2525 | 587 (also supports 465, 25) | | Webhook events | Delivery, Open, Click, Bounce, SpamComplaint, SubscriptionChange | delivered, opened, temporary\_failure, permanent\_failure | | Batch send | `/email/batch` endpoint, up to 500/request | `/bulk_messages` endpoint, recommended max 50 per request | | TLS enforcement | Optional | Always-on, cannot be disabled | | Message streams | Transactional + Broadcast streams | Single unified API (no stream concept) | ## Send a single email ```bash Postmark (before) theme={null} curl -X POST https://api.postmarkapp.com/email \ -H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "From": "provider@yourclinic.com", "To": "patient@example.com", "Subject": "Your appointment summary", "HtmlBody": "

See attached.

", "MessageStream": "outbound" }' ``` ```bash Paubox (after) theme={null} curl -X POST https://api.paubox.com/v1/email/messages \ -H "Authorization: Bearer $PAUBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "message": { "recipients": ["patient@example.com"], "headers": { "subject": "Your appointment summary", "from": "provider@yourclinic.com" }, "content": { "text/html": "

See attached.

" } } } }' ```
## SMTP configuration ```js Postmark (before) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.postmarkapp.com', port: 587, auth: { user: process.env.POSTMARK_SERVER_TOKEN, pass: process.env.POSTMARK_SERVER_TOKEN, }, }); ``` ```js Paubox (after) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.paubox.com', port: 587, auth: { user: 'apikey', pass: process.env.PAUBOX_API_KEY, }, }); ``` **Tip:** Note the username change: Postmark uses the server token as both username and password. Paubox uses the literal string `apikey` as username and your API key as password. ## Webhook event mapping | Postmark event | Paubox event key | Notes | | :------------------- | :------------------------------- | :--------------------------------------------------------------------------------------------- | | `Delivery` | `api_mail_log_delivered` | | | `Open` | `api_mail_log_opened` | | | `Bounce` (hard) | `api_mail_log_permanent_failure` | | | `Bounce` (soft) | `api_mail_log_temporary_failure` | | | `Click` | No equivalent | Remove handler or no-op; poll click data via [Get message receipt](/email-api/message-receipt) | | `SpamComplaint` | No equivalent | Remove handler | | `SubscriptionChange` | No equivalent | Manage unsubscribes in the Paubox dashboard | **Note:** Click tracking is available by polling `GET /message_receipt?sourceTrackingId=...`; it is not delivered as a push webhook event. ## Migration checklist Required before go-live. Contact Paubox to initiate the Business Associate Agreement. Add your domain on the [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) page and complete the TXT record verification. See the [Quickstart guide](/email-api/quickstart) for step-by-step instructions. From the Settings page, generate an API key. All requests use the base URL `https://api.paubox.com/v1/email`. Replace the Postmark endpoint and `X-Postmark-Server-Token` header with the Paubox endpoint and Bearer token. Restructure the request body to use the `data.message` shape shown above. Paubox has no stream concept. Remove the `"MessageStream"` field from all requests. If you use the SMTP path, update `host` to `smtp.paubox.com`, set `username` to the literal string `apikey`, and `password` to your Paubox API key. Update your webhook endpoint using the [event mapping table](#webhook-event-mapping) above. Remove handlers for `Click`, `SpamComplaint`, and `SubscriptionChange`. Paubox enforces TLS on every message. Any `allowNonTLS: true` or equivalent settings should be removed. Confirm delivery using the [Get message receipt](/email-api/message-receipt) endpoint with the `sourceTrackingId` returned from your test send. Replace Postmark SPF/DKIM records with the Paubox records shown in your Settings page. Once traffic has fully moved to Paubox, revoke your Postmark server tokens. ## Next steps Full setup walkthrough from account creation to first send Configure delivery event notifications Send up to 50 messages in a single request Connect via SMTP instead of REST # Migrate from Resend Source: https://docs.paubox.com/email-api/migrate-from-resend Switch from Resend to the Paubox Email API for HIPAA compliant transactional email. ## Why migrate Resend does not sign Business Associate Agreements (BAAs). HIPAA requires a signed BAA with any vendor that handles Protected Health Information (PHI) in transit, including transactional email providers. Paubox is purpose-built for HIPAA compliant email and signs a BAA with every customer, and TLS is always-on for every message. Resend's API uses familiar Bearer-auth and JSON patterns, so the switch is a small refactor for developers already comfortable with Resend. ## What stays the same * REST API over HTTPS with JSON request bodies * API key authentication via the `Authorization: Bearer` header * Domain authentication: SPF and DKIM records required * SMTP as an alternative to the HTTP API * Webhook-based event notifications for delivery status ## Key differences | Dimension | Resend | Paubox Email API | | :----------------- | :----------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | | Base URL | `https://api.resend.com/emails` | `https://api.paubox.com/v1/email/messages` | | Auth header | `Authorization: Bearer $RESEND_API_KEY` | `Authorization: Bearer $PAUBOX_API_KEY` | | Request body shape | `from`, `to`, `subject`, `html` flat keys | `data.message` object with `headers` and `content` keys | | Recipients field | `to` (array of strings) | `recipients` (array of strings, nested under `data.message`) | | SMTP host | `smtp.resend.com` | `smtp.paubox.com` | | SMTP username | `resend` (literal) | `apikey` (literal) | | SMTP port | 465 (recommended), 587, 25 | 587 (also supports 465, 25) | | Webhook events | email.sent, email.delivered, email.bounced, email.opened, email.clicked, email.complained, email.delivery\_delayed | delivered, opened, temporary\_failure, permanent\_failure | | Batch send | `/emails/batch` endpoint | `/bulk_messages` endpoint, recommended max 50 per request | | TLS enforcement | Optional | Always-on, cannot be disabled | | BAA available | No | Yes (signed with every customer) | ## Send a single email ```bash Resend (before) theme={null} curl -X POST https://api.resend.com/emails \ -H "Authorization: Bearer $RESEND_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "provider@yourclinic.com", "to": ["patient@example.com"], "subject": "Your appointment summary", "html": "

See attached.

" }' ``` ```bash Paubox (after) theme={null} curl -X POST https://api.paubox.com/v1/email/messages \ -H "Authorization: Bearer $PAUBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "message": { "recipients": ["patient@example.com"], "headers": { "subject": "Your appointment summary", "from": "provider@yourclinic.com" }, "content": { "text/html": "

See attached.

" } } } }' ```
## SMTP configuration ```js Resend (before) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.resend.com', port: 465, auth: { user: 'resend', pass: process.env.RESEND_API_KEY, }, }); ``` ```js Paubox (after) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.paubox.com', port: 587, auth: { user: 'apikey', pass: process.env.PAUBOX_API_KEY, }, }); ``` **Tip:** Two changes are required: update `host` from `smtp.resend.com` to `smtp.paubox.com`, and change the literal username from `resend` to `apikey`. ## Webhook event mapping | Resend event | Paubox event key | Notes | | :----------------------- | :------------------------------- | :--------------------------------------------------------------------------------------------- | | `email.delivered` | `api_mail_log_delivered` | | | `email.opened` | `api_mail_log_opened` | | | `email.bounced` | `api_mail_log_permanent_failure` | | | `email.delivery_delayed` | `api_mail_log_temporary_failure` | | | `email.clicked` | no equivalent | Remove handler or no-op; poll click data via [Get message receipt](/email-api/message-receipt) | | `email.complained` | no equivalent | Remove handler | | `email.sent` | no equivalent | Remove handler; `delivered` confirms acceptance | **Note:** Click tracking is available by polling `GET /message_receipt?sourceTrackingId=...`; it is not delivered as a push webhook event. ## Migration checklist Required before go-live. Contact Paubox to initiate the Business Associate Agreement. Add your domain on the [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) page and complete the TXT record verification. See the [Quickstart guide](/email-api/quickstart) for step-by-step instructions. From the Settings page, generate an API key. All requests use the base URL `https://api.paubox.com/v1/email`. Apply the changes shown in the [Key differences](#key-differences) table and [Send a single email](#send-a-single-email) section above. Note that recipients move from a top-level `to` array into `data.message.recipients`. If you use the SMTP path, update `host` to `smtp.paubox.com` and change the literal username from `resend` to `apikey`. See [SMTP configuration](#smtp-configuration) above. Update your webhook endpoint using the [event mapping table](#webhook-event-mapping) above. Remove handlers for `email.clicked`, `email.complained`, and `email.sent`. Paubox enforces TLS on every message. Any `allowNonTLS: true` or equivalent settings should be removed. Confirm delivery using the [Get message receipt](/email-api/message-receipt) endpoint with the `sourceTrackingId` returned from your test send. Replace Resend SPF/DKIM records with the Paubox records shown in your Settings page. Once traffic has fully moved to Paubox, revoke your Resend API keys. ## Next steps Full setup walkthrough from account creation to first send Configure delivery event notifications Send up to 50 messages in a single request Connect via SMTP instead of REST # Migrate from SendGrid Source: https://docs.paubox.com/email-api/migrate-from-sendgrid Switch from SendGrid v3 to the Paubox Email API for HIPAA compliant transactional email. ## Why migrate SendGrid does not sign Business Associate Agreements (BAAs) for standard transactional email accounts. HIPAA requires a signed BAA with any vendor that handles Protected Health Information (PHI) in transit, including email providers. Paubox is purpose-built for HIPAA compliant email and signs a BAA with every customer. The API is REST-based and follows patterns SendGrid developers will recognize, so the switch is straightforward. ## What stays the same * REST API over HTTPS with JSON request bodies * Domain authentication: SPF and DKIM records required * API key authentication via the `Authorization` header * SMTP as an alternative to the HTTP API (same `apikey` / API-key-as-password pattern) * Webhook-based event notifications for delivery status ## Key differences | Dimension | SendGrid v3 | Paubox Email API | | :----------------- | :------------------------------------------------------------------- | :-------------------------------------------------------- | | Base URL | `https://api.sendgrid.com/v3/mail/send` | `https://api.paubox.com/v1/email` | | Auth header | `Authorization: Bearer $SENDGRID_API_KEY` | `Authorization: Bearer $PAUBOX_API_KEY` | | Request body shape | `personalizations` array with nested `to`/`subject` | `data.message` object with `headers` and `content` keys | | SMTP host | `smtp.sendgrid.net` | `smtp.paubox.com` | | SMTP username | `apikey` (literal) | `apikey` (literal, same pattern) | | SMTP port | 587 | 587 (also supports 465, 25) | | Webhook events | processed, delivered, open, click, bounce, spam\_report, unsubscribe | delivered, opened, temporary\_failure, permanent\_failure | | Batch send | up to 1,000 personalizations per request | `/bulk_messages` endpoint, recommended max 50 per request | | TLS enforcement | optional (can be disabled) | always-on, cannot be disabled | ## Send a single email ```bash SendGrid (before) theme={null} curl -X POST https://api.sendgrid.com/v3/mail/send \ -H "Authorization: Bearer $SENDGRID_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "personalizations": [{"to": [{"email": "patient@example.com"}]}], "from": {"email": "provider@yourclinic.com"}, "subject": "Your appointment summary", "content": [{"type": "text/html", "value": "

See attached.

"}] }' ``` ```bash Paubox (after) theme={null} curl -X POST https://api.paubox.com/v1/email/messages \ -H "Authorization: Bearer $PAUBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "message": { "recipients": ["patient@example.com"], "headers": { "subject": "Your appointment summary", "from": "provider@yourclinic.com" }, "content": { "text/html": "

See attached.

" } } } }' ```
## SMTP configuration The only required change is `host`. The username/password pattern is identical. ```js SendGrid (before) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.sendgrid.net', port: 587, auth: { user: 'apikey', pass: process.env.SENDGRID_API_KEY, }, }); ``` ```js Paubox (after) theme={null} const transporter = nodemailer.createTransport({ host: 'smtp.paubox.com', port: 587, auth: { user: 'apikey', pass: process.env.PAUBOX_API_KEY, }, }); ``` **Tip:** `port`, `user`, and the auth pattern are unchanged. Only `host` needs to be updated. ## Webhook event mapping | SendGrid event | Paubox event key | Notes | | :--------------------------- | :------------------------------- | :--------------------------------------------------------------------------------------------- | | `delivered` | `api_mail_log_delivered` | | | `open` | `api_mail_log_opened` | | | `bounce` (hard) | `api_mail_log_permanent_failure` | | | `bounce` (soft) / `deferred` | `api_mail_log_temporary_failure` | | | `click` | No equivalent | Remove handler or no-op; poll click data via [Get message receipt](/email-api/message-receipt) | | `spam_report` | No equivalent | Remove handler | | `processed` | No equivalent | Remove handler; `delivered` confirms acceptance | | `unsubscribe` | No equivalent | Manage unsubscribes in the Paubox dashboard | **Note:** Click tracking is available by polling `GET /message_receipt?sourceTrackingId=...`; it is not delivered as a push webhook event. ## Migration checklist Required before go-live. Contact Paubox to initiate the Business Associate Agreement. Add your domain on the [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) page and complete the TXT record verification. See the [Quickstart guide](/email-api/quickstart) for step-by-step instructions. From the Settings page, generate an API key. All requests use the base URL `https://api.paubox.com/v1/email`. Apply the changes shown in the [Key differences](#key-differences) table and [Send a single email](#send-a-single-email) section above. If you use the SMTP path, update `host` to `smtp.paubox.com`. See [SMTP configuration](#smtp-configuration) above. Update your webhook endpoint using the [event mapping table](#webhook-event-mapping) above. Remove handlers for `click`, `spam_report`, `processed`, and `unsubscribe`. Paubox enforces TLS on every message. Any `allowNonTLS: true` or equivalent settings should be removed. Confirm delivery using the [Get message receipt](/email-api/message-receipt) endpoint with the `sourceTrackingId` returned from your test send. Replace SendGrid SPF/DKIM records with the Paubox records shown in your Settings page. Once traffic has fully moved to Paubox, revoke your SendGrid API keys. ## Next steps Full setup walkthrough from account creation to first send Configure delivery event notifications Send up to 50 messages in a single request Connect via SMTP instead of REST # Migrate from Amazon SES Source: https://docs.paubox.com/email-api/migrate-from-ses Switch from Amazon SES to the Paubox Email API for HIPAA compliant transactional email. ## Why migrate AWS will sign a Business Associate Agreement (BAA) that covers SES as a service, but the BAA does not protect Protected Health Information (PHI) in the body of the mail once it leaves AWS. SES TLS enforcement defaults to opportunistic (`TlsPolicy: OPTIONAL`), so messages silently fall back to plaintext when the recipient server doesn't advertise STARTTLS; the customer carries the encryption-in-transit obligation. Paubox closes that gap with always-on TLS, an automatic Secure Portal fallback when the recipient cannot accept encrypted mail, and a BAA signed with every customer. The send-side API is REST/JSON and the migration is straightforward as most patterns map one-to-one. ## What stays the same * REST API over HTTPS with JSON request bodies (when using SESv2; SES v1 form-encoded callers will move to JSON) * Domain authentication: SPF and DKIM records required * SMTP as an alternative to the HTTP API * Webhook-based event notifications for delivery status * Per-domain sending configuration ## Key differences | Dimension | Amazon SES (SESv2) | Paubox Email API | | :------------------ | :----------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Base URL | `https://email.{region}.amazonaws.com/v2/email/outbound-emails` | `https://api.paubox.com/v1/email/messages` | | Region selection | Required (`us-east-1`, `eu-west-1`, …) | Single global endpoint, no region | | Auth method | AWS SigV4 (access key + secret + region, or IAM role) | Bearer token: `Authorization: Bearer $PAUBOX_API_KEY` | | SDK | `@aws-sdk/client-sesv2`, `boto3` `sesv2`, `Aws::SESV2`, etc. | `paubox-node`, `paubox-python3`, `paubox-ruby`, `paubox-php`, `paubox-java`, `paubox-csharp`, or plain `fetch` / `requests` | | Request body shape | `Content.Simple.Subject.Data` / `Body.Html.Data` / `Destination.ToAddresses[]` | `data.message.headers.subject` / `content["text/html"]` / `recipients[]` | | Attachments | Raw MIME via `SendRawEmail` only | First-class `data.message.attachments[]` with base64 `content` | | Bulk send | `SendBulkEmail` (templated only, up to 50 destinations) | `/bulk_messages` endpoint, recommended max 50 per request | | Templates | SES templates (`CreateTemplate` API, Handlebars-style syntax) | Paubox Dynamic Templates (`/dynamic_templates` CRUD, Handlebars) | | SMTP host | `email-smtp.{region}.amazonaws.com` | `smtp.paubox.com` | | SMTP credentials | IAM-user-derived SMTP username + password (separate from the AWS API key) | Literal string `apikey` as username, Paubox API key as password | | SMTP port | 25, 465, 587, or 2587 | 587 (also supports 465, 25) | | TLS enforcement | `TlsPolicy: OPTIONAL \| REQUIRE` (per configuration set) | Always-on, cannot be disabled | | Delivery feedback | SNS topic subscription → HTTPS endpoint | Paubox webhooks (HTTPS POST directly to your endpoint) | | Message ID returned | `MessageId` | `sourceTrackingId` | ## Send a single email ```js Amazon SES (before) theme={null} import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2"; const ses = new SESv2Client({ region: process.env.AWS_REGION }); await ses.send( new SendEmailCommand({ FromEmailAddress: "provider@yourclinic.com", Destination: { ToAddresses: ["patient@example.com"] }, Content: { Simple: { Subject: { Data: "Your appointment summary" }, Body: { Html: { Data: "

See attached.

" } }, }, }, }), ); ``` ```bash Paubox (after) theme={null} curl -X POST https://api.paubox.com/v1/email/messages \ -H "Authorization: Bearer $PAUBOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "message": { "recipients": ["patient@example.com"], "headers": { "subject": "Your appointment summary", "from": "provider@yourclinic.com" }, "content": { "text/html": "

See attached.

" } } } }' ```
**Note:** SES uses AWS SigV4 over the AWS credential chain (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, or an IAM role). Paubox uses a single Bearer token. After cutover, remove the AWS SDK dependency and any AWS credentials scoped to `ses:SendEmail` from your runtime environment. ## SMTP configuration ```js Amazon SES (before) theme={null} const transporter = nodemailer.createTransport({ host: "email-smtp.us-east-1.amazonaws.com", port: 587, auth: { user: process.env.SES_SMTP_USERNAME, // IAM-derived SMTP username pass: process.env.SES_SMTP_PASSWORD, // IAM-derived SMTP password }, }); ``` ```js Paubox (after) theme={null} const transporter = nodemailer.createTransport({ host: "smtp.paubox.com", port: 587, auth: { user: "apikey", pass: process.env.PAUBOX_API_KEY, }, }); ``` **Tip:** SES SMTP credentials are not the same as your AWS API key; they are derived from a dedicated IAM user via the SES console. After traffic has moved to Paubox, delete that IAM user so the SMTP credential is fully revoked. ## Webhook event mapping SES does not push HTTP webhooks directly; bounces, complaints, and deliveries are published to an SNS topic that fans out to an HTTPS subscription. Paubox replaces both layers with a direct HTTPS POST to your endpoint. | SES SNS event | Paubox event key | Notes | | :--------------------------------- | :------------------------------- | :----------------------------------------------------------------------------- | | `Delivery` | `api_mail_log_delivered` | | | `Open` | `api_mail_log_opened` | | | `Bounce` (`bounceType: Permanent`) | `api_mail_log_permanent_failure` | | | `Bounce` (`bounceType: Transient`) | `api_mail_log_temporary_failure` | | | `Click` | no equivalent | Poll click data via [Get message receipt](/email-api/message-receipt) | | `Complaint` | no equivalent | Manage unsubscribes in the Paubox dashboard | | `Reject` | no equivalent | Paubox returns rejections synchronously in the POST response, not as a webhook | | `Send` | no equivalent | `delivered` confirms acceptance; remove handler | **Note:** Click tracking is available by polling `GET /message_receipt?sourceTrackingId=...`; it is not delivered as a push webhook event. **Note:** Paubox webhooks POST directly to your HTTPS endpoint. There is no intermediary SNS topic, so the JSON envelope is flatter than the SES → SNS → subscription payload. Configure endpoints in the Paubox dashboard under Email API → Webhooks. See the [Webhooks reference](/email-api/webhooks) for the payload shape. ## Template migration If you use SES templates (`CreateTemplate` / `SendTemplatedEmail` / `SendBulkTemplatedEmail`), recreate each one as a Paubox Dynamic Template. Both systems use Handlebars-style `{{variable}}` syntax, so most template bodies port verbatim. Run `aws ses get-template --template-name ` (or `aws sesv2 get-email-template`) and capture the `Subject`, `HtmlPart`, and `TextPart` fields. POST each template to `/dynamic_templates` with the body remapped to Paubox's shape. See the [Dynamic templates reference](/email-api/dynamic-templates) for the request format. Replace `SendTemplatedEmail` / `SendBulkTemplatedEmail` calls with `POST /messages` (or `/bulk_messages`) using the `template_id` and `template_variables` fields. ## Migration checklist Required before go-live. Contact Paubox to initiate the Business Associate Agreement. Add your domain on the [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) page and complete the TXT record verification. See the [Quickstart guide](/email-api/quickstart) for step-by-step instructions. From the Settings page, generate an API key. All requests use the base URL `https://api.paubox.com/v1/email`. Remove `@aws-sdk/client-ses` / `@aws-sdk/client-sesv2` (or the `boto3` / `Aws::SESV2` equivalent) and call Paubox via `fetch` / one of the Paubox SDKs. Restructure the request body to use the `data.message` shape shown above. Drop `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, and any IAM role permissions scoped to `ses:SendEmail` / `ses:SendRawEmail` / `ses:SendBulkEmail`. If you use the SMTP path, update `host` to `smtp.paubox.com`, set `username` to the literal string `apikey`, and `password` to your Paubox API key. Delete the IAM user that was created for SES SMTP credentials. See [Template migration](#template-migration) above. Remap your bounce / complaint / delivery handler to the Paubox webhook payload using the [event mapping table](#webhook-event-mapping) above, then delete the SNS topic and configuration-set event destination used for SES feedback. Paubox enforces TLS unconditionally; remove `TlsPolicy: OPTIONAL` or `TlsPolicy: REQUIRE` from any SES configuration set you were referencing. Confirm delivery using the [Get message receipt](/email-api/message-receipt) endpoint with the `sourceTrackingId` returned from your test send. Replace SES SPF and DKIM records with the Paubox records shown in your Settings page. Once traffic has fully moved to Paubox, delete the IAM user and access keys used by the SES integration. ## Next steps Full setup walkthrough from account creation to first send Configure delivery event notifications Send up to 50 messages in a single request Connect via SMTP instead of REST # Quickstart guide Source: https://docs.paubox.com/email-api/quickstart In this Quickstart, send your first HIPAA compliant email via Paubox Email API in under 5 minutes. It's free to sign up and to send up to 300 emails per month, with a BAA included. **Note:** If you send more than 300 emails in one month, you will be automatically moved to the appropriate pricing tier and billed the annual cost. Once you have a Paubox account, go to the Settings > Domains page and add the domain you'll be sending from. Paubox verifies domain ownership via your domain's **SPF record**. Paubox will display the SPF value to add at your DNS provider (e.g., Route 53, Cloudflare, GoDaddy). Add or update the SPF record in your domain's DNS, then return to the Settings > Domains page and click **Verify**. **Tip:** Most DNS providers propagate DNS changes within a few minutes, but allow up to 48 hours. If verification fails immediately, wait a few minutes and try again. From the Settings > API Keys page, click on the domain you would like to generate an API key for, then press the **Add API Key** button. Give the key a description and a new key will be generated upon submission. Make sure you save your key because if you lose it you will have to generate a new one. All Email API requests use the same base URL: ``` https://api.paubox.com/v1/email ``` You will use this base URL in every API request. Use the following curl command to send a test email. Replace `YOUR_API_KEY` and the `from` address with your actual values. The `from` address must match your verified domain. ```bash theme={null} curl --request POST \ --url https://api.paubox.com/v1/email/messages \ --header 'Authorization: Bearer YOUR_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "data": { "message": { "recipients": ["recipient@example.com"], "headers": { "subject": "Hello from Paubox", "from": "sender@yourdomain.com" }, "content": { "text/plain": "Hello world", "text/html": "

Hello world

" } } } }' ``` **What success looks like:** A `200 OK` response confirms your message was accepted for delivery: ```json theme={null} { "sourceTrackingId": "3d38ab13-0af8-4028-bd45-52e882e0d584", "data": "Service OK" } ``` Save the `sourceTrackingId`; you can use it to check delivery status later.
Official SDKs handle authentication and request formatting for you. Choose your language: * [C#](/csharp-sdk/quickstart) * [Go](/go-sdk/quickstart) * [Java](/java-sdk/quickstart) * [Node.js](/node-sdk/quickstart) * [Perl](/perl-sdk/quickstart) * [PHP](/php-sdk/quickstart) * [Python](/python-sdk/quickstart) * [Rails](/rails-sdk/quickstart) * [Ruby](/ruby-sdk/quickstart) * [Rust](/rust-sdk/quickstart) Prefer the terminal? See the [CLI quickstart](/cli/quickstart) to send email without writing code.
## Next steps Check delivery status using a sourceTrackingId Add files to your messages via the REST API Use Paubox from AI agents and editors via MCP # SMTP API Source: https://docs.paubox.com/email-api/smtp ## Connect to Paubox Email API via SMTP To send email through Paubox SMTP API, follow these steps: **Generate a Paubox Email API key from the** [**Paubox Email API > Settings**](https://next.paubox.com/emailapi/settings) **page.** **Configure the SMTP server host** to `smtp.paubox.com`.\ This is often labeled as the **SMTP relay** or **outgoing mail server** in most email clients. **Set your username** to the literal string `apikey`. **Note:** this should be the exact word `"apikey"`, **not** the API key you generated. **Use your API key** as the SMTP password. **Choose the appropriate port**, typically `587`, unless your network requires another. **Tip:** If you're pasting a base64-encoded API key, double-check for any extra spaces or line breaks.\ These can inadvertently appear when copying from certain terminals or editors, and they will prevent authentication.\ Since SMTP is a line-based protocol, even a stray newline can result in failure. ## Available SMTP Ports Choose the correct port based on your desired level of security: * For **TLS** connections, use:\ `25` or `587` * For **SSL/TLS** connections, use:\ `465` **Tip:** If you're not sure which one to use, **587 with STARTTLS** is the most widely supported and recommended option. ## Sending Mail via SMTP Once your SMTP connection is set up, you’re ready to construct and send email messages using your preferred client, service, or library. ## Rate Limits To avoid delivery interruptions, be aware of Paubox SMTP usage thresholds: * A single IP address may send up to **500 messages per minute**. ## Avoid Direct IP Usage Always reference the host as `smtp.paubox.com`; do **not** use direct IP addresses.\ Paubox’s infrastructure may change without warning, and relying on hardcoded IPs could cause unexpected delivery issues in the future. # Send a templated message Source: https://docs.paubox.com/email-api/templated-messages openapi1 POST /templated_messages Send an email using a dynamic template with variable substitution # Webhooks Source: https://docs.paubox.com/email-api/webhooks Receive real-time delivery event notifications from the Paubox Email API. Paubox webhooks push delivery event notifications to an HTTPS URL you own. Configure them in the [Paubox Dashboard](https://next.paubox.com/emailapi/webhooks). **Organization-wide scope:** Webhooks are triggered at the organization level. Events from **all domains** in your organization will be sent to the configured webhook URL; there is no per-domain filtering. Design your handler to inspect the `from` field in the payload if you need to route events by domain. ## Webhook fields * **URL**: You will need to provide an HTTPS URL which you own, to which you would like your webhook payload to be sent. * **Event**: The events notifications to which you would like to subscribe ## Available events | **Event Name** | **Event Name Key Value** | **Trigger** | | :---------------- | :--------------------------------- | :------------------------ | | Delivered | api\_mail\_log\_delivered | When message is delivered | | Temporary Failure | api\_mail\_log\_temporary\_failure | On soft bounce of message | | Permanent Failure | api\_mail\_log\_permanent\_failure | On hard bounce of message | | Opened | api\_mail\_log\_opened | On opening of message | ## Payloads Every Webhook notification will include an `event_name` key and a `payload` key. The type of payload you should expect is dependent on the data model that is triggering the webhook event. The most common type of payload is the `API Mail Log` payload, which is structured as follows: ```json theme={null} { "event_name": "api_mail_log_permanent_failure", "payload": { "id": 5555555555, "subject": "Hello from the Paubox Email API", "header_message_id": "", "source_tracking_id": "XXXef39e-b376-4a44-b2b9-85bdb406dXXX", "outbound_queue_id": "XXXyFY0y3Yz2XXX", "time": "2022-04-18T20:27:25.379Z", "from": "XXXXXX@XXXXXXX.com", "to": "recipient@example.com", "custom_headers": { "X-Custom-Header": "value" } } } ``` ## Retry behavior Paubox does not currently retry failed webhook deliveries. If your endpoint is unavailable when an event fires, that notification will not be re-sent. Design your endpoint to be highly available, and use the [Get message receipt](/email-api/message-receipt) endpoint to poll for status if you need guaranteed delivery tracking. ## Verifying webhook signatures Webhook signature verification is not currently supported. Use network-level controls (e.g., IP allowlisting or a shared secret in the URL path) to validate that requests to your endpoint originate from Paubox. ``` ``` # Archive a form Source: https://docs.paubox.com/forms/archive-form openapi-forms POST /api/forms/{form_id}/archive Archives a form. Archiving also deactivates the form (sets `active` to false), so it stops accepting submissions. The endpoint does not verify that the form exists: an unknown form ID still returns a 200 success response. # Authentication Source: https://docs.paubox.com/forms/authentication How to authenticate to the Paubox Forms API with a scoped API key. The Paubox Forms API has two tiers of endpoints: * **Public respondent endpoints** require no authentication. These are the endpoints called from end user devices when a respondent loads or submits a form: [Get form metadata](/forms/get-form) (`GET /public/form_data/{form_id}`) and [Submit a form response](/forms/submit-form) (`POST /api/forms/{form_id}/submissions`). The form's UUID acts as access control. * **Form management endpoints** require a Paubox API key with the `forms` scope. This covers everything else: listing, creating, updating, copying, archiving, and unarchiving forms, retrieving form statistics, and reading or exporting submissions. ## Find your credentials API keys are created in the Paubox dashboard. Copy the key when it is displayed; store it somewhere safe. Paubox API keys are scoped per product. The key must include the `forms` scope to call the Forms API. A key scoped only to other products (for example, the Email API) is rejected with `401 Unauthorized`. All Forms API requests go to the same base URL: ``` https://api.paubox.com/v1/forms ``` ## Pass credentials in requests Include the `Authorization` header with every form-management API call: ```bash theme={null} curl --request GET \ --url 'https://api.paubox.com/v1/forms/api/forms?customer_id=YOUR_CUSTOMER_ID' \ --header 'Authorization: Bearer YOUR_API_KEY' ``` The Forms API accepts Bearer tokens only: ``` Authorization: Bearer YOUR_API_KEY ``` The Forms API does not accept the `Token token=` header format used by the Paubox Marketing API. Always use `Bearer`. ## Authentication errors | Status | Meaning | | ------------------ | ------------------------------------------------------------------------------------------------------------- | | `401 Unauthorized` | The `Authorization` header is missing, the API key is invalid, or the key does not include the `forms` scope. | | `403 Forbidden` | The API key is valid, but the requested resource belongs to a different customer. | ## Key handling * Generate a new key before revoking an old one to avoid downtime. * Revoke keys immediately if they are exposed or a team member with access leaves. Never commit API keys to source control. Use environment variables or a secrets manager to inject credentials at runtime. ```bash theme={null} # Good export PAUBOX_API_KEY=your_api_key curl -H "Authorization: Bearer $PAUBOX_API_KEY" ... # Bad: do not do this curl -H "Authorization: Bearer a5e1ec4aefaa5fef1a9a2a46459eeae3503034a9" ... ``` # Copy a form Source: https://docs.paubox.com/forms/copy-form openapi-forms POST /api/forms/copy Creates a copy of an existing form with a new title. The copy starts with a submission count of 0 and no vanity URL. Returns the full new form object. # Create a form Source: https://docs.paubox.com/forms/create-form openapi-forms POST /api/forms Creates a new form. Returns the UUID of the created form. # Export a submission as CSV Source: https://docs.paubox.com/forms/export-submission-csv openapi-forms GET /api/forms/{form_id}/submissions/submission-csv/{submission_id} Exports a single submission as a CSV attachment (filename `form_data.csv`). Uses the same column layout as the full export (a "Created At" column followed by one column per form field), with a single data row. # Export a submission as PDF Source: https://docs.paubox.com/forms/export-submission-pdf openapi-forms GET /api/forms/{form_id}/submissions/{submission_id}/submission-pdf Exports a single submission as a PDF attachment (filename `form_data.pdf`). For signable forms, the PDF includes the respondent's signature images. # Export submissions as CSV Source: https://docs.paubox.com/forms/export-submissions-csv openapi-forms GET /api/forms/{form_id}/submissions/submission-csv Exports all submissions of a form as a CSV attachment (filename `form_data.csv`). The first column is "Created At", followed by one column per form field, using the field labels from the form definition. # Get form statistics Source: https://docs.paubox.com/forms/form-stats openapi-forms GET /api/forms/stats Returns aggregate form statistics for a customer: the number of active forms, the total submission count, and the number of submissions received in the last 7 days. # Get form metadata Source: https://docs.paubox.com/forms/get-form openapi-forms GET /public/form_data/{form_id} Returns the full form definition (HTML, JSON schema, CSS) for a given form. Called by the form embed before rendering a form to a respondent. No authentication required. # Get a form Source: https://docs.paubox.com/forms/get-form-details openapi-forms GET /api/forms/{form_id} Returns the full form definition by ID, including inactive and archived forms (unlike the public endpoint, which only serves renderable forms to respondents). # Paubox Forms API Source: https://docs.paubox.com/forms/index Build, host, and process HIPAA compliant forms directly inside your application. The Paubox Forms API lets you build, host, and process HIPAA compliant forms (patient intake, consent, surveys, waivers) directly inside your application. Submissions are stored on Paubox's HITRUST certified infrastructure and visible in your Paubox Forms account. The Forms API is part of Paubox Forms, Paubox's HIPAA compliant intake form product. ## What you can build with it Healthcare teams use the Paubox Forms API to: * Embed patient intake forms inside a portal or app and process responses without storing PHI on their own systems * Collect signed consent forms tied to appointments, onboarding, or treatment plans * Securely collect patient data with HIPAA compliant forms * Create, update, copy, and archive forms programmatically instead of clicking through the Paubox Forms app * Pull submissions into their own systems, or export them as CSV or PDF for records and reporting ## Available endpoints Base URL: `https://api.paubox.com/v1/forms` ### Public endpoints These endpoints are called by respondents loading and submitting forms from end user devices, so they require no API key. | Method | Endpoint | Purpose | API key | | ------ | ---------------------------------- | ---------------------------------------------------------------------------------------- | ------- | | `GET` | `/public/form_data/{form_id}` | Retrieve a form's full definition (HTML, JSON schema, CSS) for rendering to a respondent | No | | `POST` | `/api/forms/{form_id}/submissions` | Submit a form response, including text fields and file attachments | No | See the reference: [Get form metadata](/forms/get-form) and [Submit a form response](/forms/submit-form). ### Management endpoints These endpoints require an API key with the `forms` scope, sent as `Authorization: Bearer YOUR_API_KEY`. See [Authentication](/forms/authentication). | Method | Endpoint | Purpose | API key | | ------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------- | ------- | | `GET` | `/api/forms` | List your forms, with filtering, search, sorting, and pagination | Yes | | `POST` | `/api/forms` | Create a form | Yes | | `GET` | `/api/forms/{form_id}` | Get a form's full definition, including inactive and archived forms | Yes | | `PUT` | `/api/forms/{form_id}` | Update a form (partial update; omitted fields are unchanged) | Yes | | `POST` | `/api/forms/copy` | Copy an existing form under a new title | Yes | | `POST` | `/api/forms/{form_id}/archive` | Archive a form (also deactivates it) | Yes | | `POST` | `/api/forms/{form_id}/unarchive` | Unarchive a form (does not re-activate it) | Yes | | `GET` | `/api/forms/stats` | Get form statistics for a customer: active form count and submission counts | Yes | | `GET` | `/api/forms/{form_id}/submissions` | List submissions for a form, with sorting and pagination | Yes | | `GET` | `/api/forms/{form_id}/submissions/submission-csv` | Export all submissions of a form as CSV | Yes | | `GET` | `/api/forms/{form_id}/submissions/submission-csv/{submission_id}` | Export a single submission as CSV | Yes | | `GET` | `/api/forms/{form_id}/submissions/{submission_id}/submission-pdf` | Export a single submission as PDF | Yes | See the reference: [List forms](/forms/list-forms), [Create a form](/forms/create-form), [Get a form](/forms/get-form-details), [Update a form](/forms/update-form), [Copy a form](/forms/copy-form), [Archive a form](/forms/archive-form), [Unarchive a form](/forms/unarchive-form), [Get form statistics](/forms/form-stats), [List form submissions](/forms/list-submissions), [Export submissions as CSV](/forms/export-submissions-csv), [Export a submission as CSV](/forms/export-submission-csv), and [Export a submission as PDF](/forms/export-submission-pdf). ## How it handles HIPAA and security The Paubox Forms API runs on the same HIPAA compliant infrastructure as the rest of the Paubox platform. Form definitions and submissions are stored in Paubox's secure environment. Paubox signs a business associate agreement (BAA) with every customer. The two public endpoints are called by respondents loading and submitting forms from end user devices, where authentication wouldn't be feasible. The form's UUID acts as access control: * Form IDs are UUIDs, which makes them difficult to enumerate * Submissions are capped at 250 MB total, including form fields and any file attachments All management endpoints require an API key with the `forms` scope, and a key can only access forms belonging to its own customer account. Paubox Forms is included with paid Paubox accounts, including Paubox Email Suite. ## Authentication | Endpoint | Authentication | | --------------------------------------- | ----------------------------------------------------------- | | `GET /public/form_data/{form_id}` | Public, no API key required | | `POST /api/forms/{form_id}/submissions` | Public, no API key required | | All management endpoints | `Authorization: Bearer YOUR_API_KEY` with the `forms` scope | The two public endpoints are intentionally unauthenticated. Respondents fill out forms from end user devices, so authentication happens at the form definition layer rather than the request layer. Management endpoints (listing, creating, updating, copying, archiving forms, and reading or exporting submissions) require a scoped API key generated in the Paubox dashboard. The key must carry the `forms` scope; a key without it receives a `401 Unauthorized` response, and a valid key requesting another customer's resources receives `403 Forbidden`. See [Authentication](/forms/authentication) for details. ## Get started 1. Create a form in the Paubox Forms app, or generate an API key with the `forms` scope and create one with `POST /api/forms`. 2. Copy the form's UUID. This is the `form_id` you'll pass to the endpoints. 3. Use the public endpoints to render the form and accept submissions, and the management endpoints to read and export what comes in. Retrieve the form's HTML, JSON schema, and CSS for rendering to a respondent. Post field values and file attachments to the submissions endpoint. Generate a scoped API key and authenticate to the management endpoints. Retrieve submissions programmatically, or export them as CSV or PDF. ## FAQs Yes. Form definitions and submissions are stored on Paubox's HITRUST certified, HIPAA compliant infrastructure. All data is encrypted in transit and at rest, and Paubox signs a business associate agreement (BAA) with every customer. The two public endpoints are called by respondents loading and submitting forms from end user devices, where authentication wouldn't be feasible. The form's UUID acts as access control: each form has a unique UUID generated by Paubox when you create the form. All management endpoints require an API key with the `forms` scope. Yes. Forms can be marked as signable. The `signable` and `signature_confirmation_label` fields on the form metadata indicate signature behavior, and a signature confirmation is recorded with the submission. PDF exports of submissions include the signature image. Yes. The `attachments` array on `POST /api/forms/{form_id}/submissions` accepts file objects with a `name` and base64 encoded `content`. The maximum total submission size is 250 MB. Submissions are stored in your Paubox Forms account and visible in the app. You can configure email notifications to designated recipients on each submission, retrieve submissions with `GET /api/forms/{form_id}/submissions`, or export them as CSV or PDF. It depends on the endpoint. The public form fetch (`GET /public/form_data/{form_id}`), update, copy, list submissions, and both CSV export endpoints return `404 Not Found`. The management `GET /api/forms/{form_id}`, the public submission endpoint, and the PDF export currently return `500` for an unknown form ID. The archive and unarchive endpoints do not verify that the form exists and return a `200` success response either way. The `form_data` object on a submission accepts key-value pairs where keys match the field names defined in the form's schema. Retrieve the schema by calling `GET /public/form_data/{form_id}` and reading the `form_json` field. Test against any form in your Paubox Forms account. Deactivate or archive the form when you're done testing to keep submission counts clean. ## Community & support Ask usage questions in the Paubox Community. Propose features and improvements. Never post PHI, recipient addresses, or message content in public threads. Account, billing, or anything sensitive goes to [support@paubox.com](mailto:support@paubox.com). # List forms Source: https://docs.paubox.com/forms/list-forms openapi-forms GET /api/forms Returns a paginated list of forms belonging to a customer. Supports filtering by form ID, title/description search, and archived/active state, plus ordering and pagination. # List form submissions Source: https://docs.paubox.com/forms/list-submissions openapi-forms GET /api/forms/{form_id}/submissions Returns a paginated list of submissions for a form. Each submission's `form_data` field is a JSON-encoded string of the respondent's answers. # Submit a form response Source: https://docs.paubox.com/forms/submit-form openapi-forms POST /api/forms/{form_id}/submissions Submits a respondent's answers for a form. No authentication required. On success, the service stores the submission, increments the form's submission count, emails recipients (if configured), and returns 201 with no body. Maximum request size is **250 MB** (to support file attachments). # Unarchive a form Source: https://docs.paubox.com/forms/unarchive-form openapi-forms POST /api/forms/{form_id}/unarchive Unarchives a form. This does not re-activate it: `active` stays false until the form is updated with `active: true`. The endpoint does not verify that the form exists: an unknown form ID still returns a 200 success response. # Update a form Source: https://docs.paubox.com/forms/update-form openapi-forms PUT /api/forms/{form_id} Updates a form. This is a partial update: any fields omitted from the request body are left unchanged. # Authentication Source: https://docs.paubox.com/go-sdk/authentication Configure your Paubox API credentials for the Go SDK. ## Obtain your API key The Email API client requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Create a client Pass the API key directly to `paubox.New`: ```go theme={null} import ( "os" paubox "github.com/paubox/paubox-go" ) client, err := paubox.New( os.Getenv("PAUBOX_API_KEY"), ) if err != nil { log.Fatal(err) } ``` The SDK sets the `Authorization` header automatically on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Environment variables The SDK does not read environment variables automatically; you must read them and pass the value to `paubox.New`. The convention used throughout this documentation is `PAUBOX_API_KEY`. ## Forms client The Forms client (`paubox.NewFormsClient`) requires no credentials. Form submissions are associated with the form ID, not an API key. ```go theme={null} fc, err := paubox.NewFormsClient() if err != nil { log.Fatal(err) } ``` ## Security notes * Never hard-code credentials in source files. * The SDK never logs request bodies, response bodies, or the `Authorization` header. * Clients are safe to create once and reuse across goroutines for the lifetime of your application. # Email client Source: https://docs.paubox.com/go-sdk/email-client Full reference for the Paubox Go SDK Email API client: sending messages, batch sending, delivery tracking, dynamic templates, and error handling. ## Creating a client ```go theme={null} client, err := paubox.New(apiKey, ...opts) ``` `paubox.New` accepts optional functional options: | Option | Description | | -------------------------- | ---------------------------------------------- | | `paubox.WithBaseURL(url)` | Override the API base URL (useful for testing) | | `paubox.WithHTTPClient(c)` | Supply a custom `*http.Client` | | `paubox.WithTimeout(d)` | Set a per-request timeout (default: 30 s) | | `paubox.WithRetry(cfg)` | Configure retry behavior (see below) | | `paubox.WithUserAgent(s)` | Append a string to the `User-Agent` header | ### Retry configuration By default the client retries `GET` requests up to 3 times on `429` and `5xx` responses, with exponential backoff between 500 ms and 30 s. ```go theme={null} client, err := paubox.New(apiKey, paubox.WithRetry(paubox.RetryConfig{ MaxAttempts: 4, WaitMin: 200 * time.Millisecond, WaitMax: 5 * time.Second, RetryNonIdempotent: false, // set true to also retry POST/PATCH }), ) ``` ## Send a message ```go theme={null} resp, err := client.SendMessage(ctx, &paubox.SendMessageRequest{ Message: msg, OverrideOpenTracking: true, // optional }) ``` ### Message fields ```go theme={null} paubox.Message{ Recipients: []string{"alice@example.com"}, // required; To recipients CC: []string{"manager@example.com"}, // optional BCC: []string{"audit@example.com"}, // optional Headers: paubox.MessageHeaders{ From: "sender@yourdomain.com", // required Subject: "Your results are ready", // required ReplyTo: "support@yourdomain.com", // optional ListUnsubscribe: "", // optional }, Content: paubox.MessageContent{ PlainText: paubox.Ptr("Plain text body."), // at least one required HTML: paubox.Ptr("

HTML body.

"), }, Attachments: []paubox.Attachment{ { FileName: "report.pdf", ContentType: "application/pdf", Content: base64EncodedBytes, // base64-encoded file content }, }, AllowNonTLS: false, // default false: enforce TLS ForceSecureNotification: paubox.Ptr(true), // optional } ``` The `paubox.Ptr[T]` helper creates a pointer to any value, which is required for optional fields typed as `*T`: ```go theme={null} paubox.Ptr("some string") // returns *string paubox.Ptr(true) // returns *bool ``` ### Response ```go theme={null} type SendMessageResponse struct { SourceTrackingID string Data SendMessageData } ``` ## Send a batch Send up to 50 messages in a single API call. Each message gets its own tracking ID. ```go theme={null} resp, err := client.SendBatch(ctx, &paubox.SendBatchRequest{ Messages: []paubox.Message{ { Recipients: []string{"alice@example.com"}, Headers: paubox.MessageHeaders{From: "f@yourdomain.com", Subject: "Hi Alice"}, Content: paubox.MessageContent{PlainText: paubox.Ptr("Hello Alice")}, }, { Recipients: []string{"bob@example.com"}, Headers: paubox.MessageHeaders{From: "f@yourdomain.com", Subject: "Hi Bob"}, Content: paubox.MessageContent{PlainText: paubox.Ptr("Hello Bob")}, }, }, }) if err != nil { log.Fatal(err) } for i, msg := range resp.Messages { fmt.Printf("[%d] tracking ID: %s\n", i, msg.SourceTrackingID) } ``` ## Check delivery status ```go theme={null} disp, err := client.GetEmailDisposition(ctx, sourceTrackingID) ``` `disp.Data.Message.MessageDeliveries` is a slice of per-recipient records: ```go theme={null} for _, d := range disp.Data.Message.MessageDeliveries { fmt.Printf("%s → %s (opened: %d)\n", d.Recipient, d.Status.DeliveryStatus, d.OpenedCount, ) } ``` Common `DeliveryStatus` values: `delivered`, `opened`, `failed`, `pending`. ## Dynamic templates Templates use [Handlebars](https://handlebarsjs.com) syntax (`{{variable_name}}`). ### Create a template ```go theme={null} tmpl, err := client.CreateTemplate(ctx, &paubox.CreateTemplateRequest{ Name: "appointment-confirmation", Body: []byte(`

Hello {{first_name}}, your appointment is on {{date}} at {{time}}.

`), }) ``` ### List templates ```go theme={null} list, err := client.ListTemplates(ctx) for _, t := range list.Templates { fmt.Println(t.ID, t.Name) } ``` ### Get a template ```go theme={null} tmpl, err := client.GetTemplate(ctx, "template-id") ``` ### Update a template ```go theme={null} tmpl, err := client.UpdateTemplate(ctx, "template-id", &paubox.UpdateTemplateRequest{ Name: "appointment-confirmation-v2", Body: []byte(`

Updated body for {{first_name}}.

`), }) ``` ### Delete a template ```go theme={null} _, err = client.DeleteTemplate(ctx, "template-id") ``` ### Send a templated message ```go theme={null} resp, err := client.SendTemplatedMessage(ctx, &paubox.SendTemplatedMessageRequest{ TemplateName: "appointment-confirmation", TemplateValues: map[string]any{ "first_name": "Jane", "date": "2024-03-15", "time": "2:00 PM", }, Message: paubox.TemplatedMessage{ Recipients: []string{"jane@example.com"}, Headers: paubox.MessageHeaders{ From: "appointments@yourclinic.com", Subject: "Your appointment is confirmed", }, }, }) ``` ## Error handling All methods return a typed `error`. Use `errors.As` to inspect details and `errors.Is` to match sentinels: ```go theme={null} resp, err := client.SendMessage(ctx, req) if err != nil { // Match a specific condition if errors.Is(err, paubox.ErrUnauthorized) { log.Fatal("invalid API key") } if errors.Is(err, paubox.ErrRateLimit) { log.Fatal("rate limited: back off and retry") } // Inspect the full error var apiErr *paubox.PauboxError if errors.As(err, &apiErr) { fmt.Printf("HTTP %d: %s - %s (request ID: %s)\n", apiErr.StatusCode, apiErr.Title, apiErr.Details, apiErr.RequestID) } log.Fatal(err) } ``` ### Sentinel errors | Sentinel | HTTP status | | ------------------------ | ----------- | | `paubox.ErrBadRequest` | 400 | | `paubox.ErrUnauthorized` | 401 | | `paubox.ErrForbidden` | 403 | | `paubox.ErrNotFound` | 404 | | `paubox.ErrRateLimit` | 429 | | `paubox.ErrServerError` | 5xx | # Forms client Source: https://docs.paubox.com/go-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Go SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Creating a client ```go theme={null} fc, err := paubox.NewFormsClient() if err != nil { log.Fatal(err) } ``` Optional configuration is available via `FormsOption` values: | Option | Description | | ------------------------------- | --------------------------------- | | `paubox.WithFormsBaseURL(url)` | Override the Forms API base URL | | `paubox.WithFormsHTTPClient(c)` | Supply a custom `*http.Client` | | `paubox.WithFormsTimeout(d)` | Set a per-request timeout | | `paubox.WithFormsRetry(cfg)` | Configure retry behavior | | `paubox.WithFormsUserAgent(s)` | Append to the `User-Agent` header | ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```go theme={null} ctx := context.Background() form, err := fc.GetForm(ctx, "your-form-uuid") if err != nil { log.Fatal(err) } fmt.Println("Form:", form.Title) for _, field := range form.FormJSON.Body { fmt.Printf(" [%s] id=%s label=%s\n", field.Type, field.ID, field.Label) } ``` The `Form` struct includes: | Field | Description | | ---------- | ------------------------------------------------------ | | `Title` | Display name of the form | | `FormJSON` | Parsed field schema (`FormJSON.Body` is `[]FormField`) | | `HTML` | Rendered HTML for embedding | | `CSS` | Associated stylesheet | ## Submit a form ```go theme={null} _, err = fc.SubmitForm(ctx, "your-form-uuid", paubox.FormSubmission{ FormData: map[string]any{ "name": "Jane Smith", "email": "jane@example.com", "dob": "1990-01-15", }, }) if err != nil { log.Fatal(err) } ``` ### Submitting with file attachments Attach files by base64-encoding their content: ```go theme={null} import ( "encoding/base64" "os" ) fileBytes, err := os.ReadFile("consent.pdf") if err != nil { log.Fatal(err) } _, err = fc.SubmitForm(ctx, "your-form-uuid", paubox.FormSubmission{ FormData: map[string]any{ "name": "Jane Smith", }, Attachments: []paubox.FormAttachment{ { Name: "consent.pdf", Content: base64.StdEncoding.EncodeToString(fileBytes), }, }, }) ``` The `FormAttachment` struct: | Field | Type | Description | | --------- | -------- | ----------------------------- | | `Name` | `string` | File name including extension | | `Content` | `string` | Base64-encoded file content | # Paubox Go SDK Source: https://docs.paubox.com/go-sdk/index Send HIPAA compliant email and submit Paubox Forms from Go applications using the official Paubox Go SDK. The Paubox Go SDK is the official Go client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). It provides a type-safe, idiomatic Go interface for sending encrypted email, managing dynamic templates, tracking delivery, and submitting forms, with no external dependencies. ```bash theme={null} go get github.com/paubox/paubox-go ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Send batches of up to 50 messages in a single API call * Track delivery status and open/click engagement per recipient * Create, update, and delete dynamic Handlebars templates * Send templated messages with per-message variable substitution * Retrieve Paubox Form schemas and submit form responses with file attachments * Configure automatic retries with exponential backoff and jitter ## Requirements * **Go** 1.22 or later * No external runtime dependencies; the SDK uses only the Go standard library ## Get started Create a client and send your first email in minutes. Obtain and configure your API key. Full reference for sending email, managing templates, and tracking delivery. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/go-sdk/quickstart Install the Paubox Go SDK and send your first HIPAA compliant email in minutes. Add the SDK to your Go module: ```bash theme={null} go get github.com/paubox/paubox-go ``` The SDK requires Go 1.22 or later and has no external dependencies. Export your Paubox API key as an environment variable: ```bash theme={null} export PAUBOX_API_KEY="YOUR_API_KEY" ``` To obtain your API key, see [Authentication](/go-sdk/authentication). Create a file `main.go` with the following code: ```go theme={null} package main import ( "context" "fmt" "log" "os" paubox "github.com/paubox/paubox-go" ) func main() { client, err := paubox.New( os.Getenv("PAUBOX_API_KEY"), ) if err != nil { log.Fatal(err) } ctx := context.Background() resp, err := client.SendMessage(ctx, &paubox.SendMessageRequest{ Message: paubox.Message{ Recipients: []string{"recipient@example.com"}, Headers: paubox.MessageHeaders{ From: "sender@yourdomain.com", Subject: "Your first Paubox email", }, Content: paubox.MessageContent{ PlainText: paubox.Ptr("This message was sent with the Paubox Go SDK."), }, }, }) if err != nil { log.Fatal(err) } fmt.Println("Sent. Tracking ID:", resp.SourceTrackingID) } ``` Run it: ```bash theme={null} go run main.go ``` ``` Sent. Tracking ID: abc123-def456-... ``` Use the tracking ID returned by `SendMessage` to check delivery: ```go theme={null} disp, err := client.GetEmailDisposition(ctx, resp.SourceTrackingID) if err != nil { log.Fatal(err) } for _, d := range disp.Data.Message.MessageDeliveries { fmt.Printf("%s → %s\n", d.Recipient, d.Status.DeliveryStatus) } ``` ``` recipient@example.com → delivered ``` ## Next steps * [Email client](/go-sdk/email-client): send batches, attach files, manage dynamic templates, handle errors * [Forms client](/go-sdk/forms-client): retrieve form schemas and submit responses # Authentication Source: https://docs.paubox.com/java-sdk/authentication Configure your Paubox API credentials for the Java SDK. ## Obtain your API key The Email API service requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Create config.properties Create a `config.properties` file in your project directory: ```properties theme={null} APIKEY=YOUR_API_KEY ``` ## Load credentials at startup Call `ConfigurationManager.getProperties` once before creating an `EmailService`. Pass the file path relative to your working directory or as an absolute path: ```java theme={null} import com.paubox.config.ConfigurationManager; ConfigurationManager.getProperties("config.properties"); ``` The SDK reads `APIKEY` from the loaded properties and sets the `Authorization` header automatically on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## FormsService `FormsService` requires no credentials and no config loading: ```java theme={null} import com.paubox.service.FormsService; FormsService formsService = new FormsService(); ``` ## Security notes * Do not commit `config.properties` to source control. Add it to `.gitignore`. * In production environments, consider reading credentials from system environment variables and writing them to the properties file at startup rather than storing them statically on disk. # Email client Source: https://docs.paubox.com/java-sdk/email-client Full reference for the Paubox Java SDK email client: building messages, sending email, tracking delivery, and error handling. ## Instantiation After loading credentials with `ConfigurationManager.getProperties(...)`, create the service: ```java theme={null} import com.paubox.service.EmailService; EmailService service = new EmailService(); ``` ## Building a message Messages are built by setting properties on `Message`, `Header`, and `Content` objects, then wiring them together: ```java theme={null} import com.paubox.data.*; Header header = new Header(); header.setFrom("sender@yourdomain.com"); // required header.setSubject("Your results are ready"); // required header.setReplyTo("support@yourdomain.com"); // optional Content content = new Content(); content.setPlainText("Plain text body."); // at least one required content.setHtmlText("

HTML body.

"); // optional; auto base64-encoded Message message = new Message(); message.setRecipients(new String[]{"alice@example.com"}); // required message.setHeader(header); message.setContent(content); message.setCc(new String[]{"manager@example.com"}); // optional message.setBcc(new String[]{"audit@example.com"}); // optional message.setAllowNonTLS(false); // optional; default false message.setForceSecureNotification("true"); // optional ``` ### Attachments Base64-encode the file content before setting it on the attachment: ```java theme={null} import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.ArrayList; byte[] fileBytes = Files.readAllBytes(Paths.get("report.pdf")); String encoded = Base64.getEncoder().encodeToString(fileBytes); Attachment attachment = new Attachment(); attachment.setFileName("report.pdf"); attachment.setContentType("application/pdf"); attachment.setContent(encoded); ArrayList attachments = new ArrayList<>(); attachments.add(attachment); message.setAttachments(attachments); ``` ## Send a message ```java theme={null} SendMessageResponse response = service.sendMessage(message); System.out.println("Tracking ID: " + response.getSourceTrackingId()); ``` The `SendMessageResponse` fields: | Method | Description | | ----------------------- | --------------------------------------------- | | `getSourceTrackingId()` | Use to check delivery status | | `getData()` | Raw response data | | `getErrors()` | List of `Error` objects if the request failed | ## Check delivery status ```java theme={null} GetEmailDispositionResponse disposition = service.getEmailDisposition(response.getSourceTrackingId()); for (MessageDeliveries delivery : disposition.getData().getMessage().getMessageDeliveries()) { System.out.println( delivery.getRecipient() + " → " + delivery.getStatus().getDeliveryStatus() ); } ``` Common `deliveryStatus` values: `delivered`, `opened`, `failed`, `pending`. ## Error handling Both `sendMessage` and `getEmailDisposition` declare `throws Exception`. Wrap calls in a try/catch block: ```java theme={null} try { SendMessageResponse response = service.sendMessage(message); System.out.println("Sent: " + response.getSourceTrackingId()); if (response.getErrors() != null && !response.getErrors().isEmpty()) { for (Error error : response.getErrors()) { System.err.println("Error " + error.getCode() + ": " + error.getTitle()); } } } catch (Exception e) { System.err.println("Send failed: " + e.getMessage()); } ``` # Forms client Source: https://docs.paubox.com/java-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Java SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Instantiation ```java theme={null} import com.paubox.service.FormsService; FormsService service = new FormsService(); ``` No config loading is required. ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```java theme={null} import com.paubox.data.Form; Form form = service.getForm("your-form-uuid"); System.out.println("Title: " + form.getTitle()); System.out.println("HTML: " + form.getFormHtml()); ``` Key `Form` getters: | Method | Description | | ---------------------- | ----------------------------------------- | | `getTitle()` | Display name of the form | | `getFormJson()` | Parsed field schema | | `getFormHtml()` | Rendered HTML for embedding | | `getFormCss()` | Associated stylesheet | | `isActive()` | Whether the form is accepting submissions | | `getSubmissionCount()` | Number of submissions received | ## Submit a form ```java theme={null} import com.paubox.data.FormSubmissionRequest; import java.util.HashMap; import java.util.Map; Map formData = new HashMap<>(); formData.put("first_name", "Jane"); formData.put("last_name", "Smith"); formData.put("email", "jane@example.com"); FormSubmissionRequest request = new FormSubmissionRequest(); request.setFormData(formData); service.submitForm("your-form-uuid", request); ``` `submitForm` returns `void` on success (HTTP 201) and throws on failure. ### Submitting with file attachments The maximum total request size is 250 MB. ```java theme={null} import com.paubox.data.FormSubmissionAttachment; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.ArrayList; import java.util.List; byte[] fileBytes = Files.readAllBytes(Paths.get("consent.pdf")); String encoded = Base64.getEncoder().encodeToString(fileBytes); FormSubmissionAttachment attachment = new FormSubmissionAttachment(); attachment.setName("consent.pdf"); attachment.setContent(encoded); List attachments = new ArrayList<>(); attachments.add(attachment); FormSubmissionRequest request = new FormSubmissionRequest(); request.setFormData(formData); request.setAttachments(attachments); service.submitForm("your-form-uuid", request); ``` ## Error handling ```java theme={null} try { Form form = service.getForm("your-form-uuid"); service.submitForm("your-form-uuid", request); } catch (Exception e) { System.err.println("Forms error: " + e.getMessage()); } ``` `getForm` and `submitForm` throw `Exception` on HTTP 400 (bad request) and 404 (form not found). # Paubox Java SDK Source: https://docs.paubox.com/java-sdk/index Send HIPAA compliant email and submit Paubox Forms from Java applications using the official Paubox Java SDK. The Paubox Java SDK is the official Java client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). Add the JAR to your project and send encrypted, HIPAA compliant email with a few lines of Java. ## Installation Download or clone the repository and add `Paubox.Email.API.jar` to your project's classpath. **Eclipse / IntelliJ IDEA:** Right-click your project → Build Path / Dependencies → Add External JARs → select `Paubox.Email.API.jar`. **Maven (local JAR):** ```xml theme={null} com.paubox paubox-email-api 1.0 system ${project.basedir}/lib/Paubox.Email.API.jar ``` Apache HttpClient and Jackson are bundled in the JAR; no additional dependencies are required. ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Add CC, BCC, attachments, and HTML or plain-text content * Track delivery status and engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * Java (any modern version) ## Get started Add the JAR, configure credentials, and send your first email in minutes. Set up your API key in a config.properties file. Full reference for building messages, sending email, and checking delivery. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/java-sdk/quickstart Add the Paubox Java SDK and send your first HIPAA compliant email in minutes. Download `Paubox.Email.API.jar` from the repository and add it to your project's classpath. See [Installation](/java-sdk) for IDE and Maven instructions. Create a `config.properties` file in your project: ```properties theme={null} APIKEY=YOUR_API_KEY ``` To obtain your API key, see [Authentication](/java-sdk/authentication). ```java theme={null} import com.paubox.service.EmailService; import com.paubox.data.*; import com.paubox.config.ConfigurationManager; public class SendEmail { public static void main(String[] args) throws Exception { // Load credentials from config.properties ConfigurationManager.getProperties("config.properties"); // Build the message Header header = new Header(); header.setFrom("sender@yourdomain.com"); header.setSubject("Your first Paubox email"); Content content = new Content(); content.setPlainText("This message was sent with the Paubox Java SDK."); Message message = new Message(); message.setRecipients(new String[]{"recipient@example.com"}); message.setHeader(header); message.setContent(content); // Send EmailService service = new EmailService(); SendMessageResponse response = service.sendMessage(message); System.out.println("Sent. Tracking ID: " + response.getSourceTrackingId()); } } ``` ```java theme={null} GetEmailDispositionResponse disposition = service.getEmailDisposition(response.getSourceTrackingId()); for (MessageDeliveries delivery : disposition.getData().getMessage().getMessageDeliveries()) { System.out.println(delivery.getRecipient() + " → " + delivery.getStatus().getDeliveryStatus()); } ``` ## Next steps * [Email client](/java-sdk/email-client): CC/BCC, attachments, error handling * [Forms client](/java-sdk/forms-client): retrieve form schemas and submit responses # Overview Source: https://docs.paubox.com/marketing Base URL, authentication, core concepts, and conventions for the Paubox Marketing API. ## Base URL `https://api.paubox.com/v1/marketing` ## Authorization Include an `Authorization` header with every request: `Authorization: Token token=YOUR_API_KEY` Replace `YOUR_API_KEY` with your API key. Generate your key on the [Paubox Marketing > Settings](https://next.paubox.com/marketing/settings) page (note: each API key is displayed only once upon creation): ![](https://docs.paubox.com/services/api/attachments/07715022-3347-4753-9125-2418540efb57) ## Example call ```bash theme={null} curl -X POST \ https://api.paubox.com/v1/marketing/subscribers \ -H 'Authorization: Token token=YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "subscriber": { "email": "recipient@example.com", "first_name": "Jane", "last_name": "Smith" } }' ``` ## Core concepts Four resources cover most of what the Marketing API does. Understanding how they relate makes the rest of the reference easier to navigate. | Resource | What it is | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | **Campaign mailing** | The email itself — subject, HTML body, text body. Creating one only stores content; it sends nothing. | | **Subscriber** | A person in your account, identified by email address. | | **List** | Who receives a campaign. Either a *subscription list* (explicit membership) or a *dynamic list* (membership computed from saved filters). | | **Subscription** | The link between one subscriber and one list. This is where a list-level opt-out is recorded. | A **send** ties them together: it takes one campaign mailing, one list (or an explicit set of recipient addresses), and delivers the mailing to that audience. The same mailing can be sent more than once, and each send is tracked separately in analytics. **Subscription lists vs. dynamic lists:** Membership in a subscription list is explicit — you add and remove subscribers yourself. Membership in a dynamic list is derived from filters saved on the list, so it changes as your subscriber data changes. Endpoints that act on a list in bulk come in two variants for this reason: `bulk_global_*` for subscription lists, `dynamic_bulk_*` for dynamic lists. ## Send a campaign [Create a campaign](/marketing/campaigns/create) with your subject and content. `subject` is required and must be unique within your account. ```bash theme={null} curl -X POST \ https://api.paubox.com/v1/marketing/campaign_mailings \ -H 'Authorization: Token token=YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "campaign_mailing": { "subject": "March newsletter", "html_part": "
Hello
", "text_part": "Hello", "template_type": "html" } }' ``` The response contains the new mailing's ID, which you will need for every step that follows: ```json theme={null} { "data": { "id": "123e4567-e89b-12d3-a456-426614174000" } } ``` An unsubscribe footer is appended to `html_part` automatically, so you do not need to add one yourself.
[Send a test email](/marketing/campaigns/send-test-email) to a single address to check rendering before you send to a list. The subject is prefixed with `[Test]`, and test sends are not recorded in analytics. ```bash theme={null} curl -X GET \ 'https://api.paubox.com/v1/marketing/campaign_mailings/123e4567-e89b-12d3-a456-426614174000/send_test_email?to_email=me@example.com' \ -H 'Authorization: Token token=YOUR_API_KEY' ``` A successful test send returns `204 No Content` with an empty body. [Update the campaign](/marketing/campaigns/update) to change any field. Only the fields you send are modified. [Send the campaign](/marketing/campaigns/send) to go out now, or [schedule it](/marketing/campaigns/schedule) for a future time. Both take the campaign mailing ID plus a target — a `subscription_list_id`, a `dynamic_list_id`, or an explicit list of `recipient_emails`. **Sending is asynchronous.** A successful response means the send was accepted and queued, not that delivery has finished. Track progress through the [campaign analytics](/marketing/analytics/campaigns) endpoints rather than the send response. Use the analytics endpoints to see how the campaign performed: [send totals](/marketing/analytics/campaigns), [per-send results](/marketing/analytics/campaign-sends), [individual deliveries](/marketing/analytics/deliveries), and [tracking link engagement](/marketing/analytics/tracking-links).
**Tip:** A brand-new Paubox Marketing account may be temporarily prevented from scheduling its first campaign as an anti-abuse measure. If you hit a `403` with a message about new accounts, contact [support@paubox.com](mailto:support@paubox.com) and they can clear it for you. ## Manage opt-ins and opt-outs Paubox Marketing records two independent levels of opt-out, and it matters which one you use. | Level | Where it lives | Effect | | -------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | **List-level** | `unsubscribed_at` on the subscription | The subscriber stops receiving campaigns sent to that one list. They still receive campaigns sent to other lists. | | **Global** | `opted_out_on` on the subscriber | The subscriber receives nothing, across every list. | Which one an endpoint applies depends on whether you send `subscription_list_ids`: * [`POST /subscriptions/unsubscribe`](/marketing/subscriptions/unsubscribe) **with** `subscription_list_ids` records a list-level opt-out. * The same endpoint **without** `subscription_list_ids` records a global opt-out. * [`POST /subscriptions/subscribe`](/marketing/subscriptions/subscribe) always clears the global opt-out, because a subscriber who is subscribed to any list is by definition not globally unsubscribed. Treat a global opt-out as permanent unless the recipient asks to be resubscribed. Re-subscribing someone who opted out, without a new request from them, is exactly the pattern that damages sending reputation and can put you out of compliance with anti-spam rules. For a single subscriber and list you already have a subscription record for, [deleting the subscription](/marketing/subscriptions/delete) is the most direct route — it stamps `unsubscribed_at` and leaves everything else untouched. ### Acting on a whole list When you want to opt out an entire list rather than a known set of subscribers, use the bulk endpoints. They accept a `from_subscription_list_id` and operate on everyone in that list, minus any `except_ids` you supply — useful for "select all except these" flows. * Subscription lists: [bulk global subscribe](/marketing/subscriptions/bulk-global-subscribe) and [bulk global unsubscribe](/marketing/subscriptions/bulk-global-unsubscribe) * Dynamic lists: [bulk subscribe](/marketing/subscriptions/dynamic-bulk-subscribe) and [bulk unsubscribe](/marketing/subscriptions/dynamic-bulk-unsubscribe) These endpoints run in the background. See [Background jobs](#background-jobs) below. ## API conventions ### Identifiers Records are identified by UUID. A `campaign_mailing_id`, `subscription_list_id`, or subscriber ID in a URL or request body is always the UUID form: ``` 123e4567-e89b-12d3-a456-426614174000 ``` See [Locating parameter values](/marketing/parameter-values) for where to find each one in the dashboard. **One exception:** [`POST /subscriptions`](/marketing/subscriptions/create) takes the subscriber's internal numeric ID rather than the UUID. To add a subscriber to a list using the UUID you already have, [create the subscriber](/marketing/subscribers/create) with a `subscription_list_id` instead, or use [`POST /subscriptions/subscribe`](/marketing/subscriptions/subscribe). ### Response shapes Most read endpoints return resources in JSON:API form — an `id`, a `type`, and the fields nested under `attributes`: ```json theme={null} { "data": { "id": "123e4567-e89b-12d3-a456-426614174000", "type": "campaign_mailing", "attributes": { "subject": "March newsletter", "created_at": "2026-03-01T12:00:00Z" } } } ``` Write endpoints are terser. Creating or updating a campaign mailing returns only the ID, not the full record: ```json theme={null} { "data": { "id": "123e4567-e89b-12d3-a456-426614174000" } } ``` List and detail responses for the same resource do not always carry the same fields. Listing campaign mailings includes aggregate counts (`sent_count`, `delivered_count`, and so on) but omits the content; fetching a single one includes `html_part`, `text_part`, and `form_data` but omits the counts. Each reference page documents its own response. ### Errors **Check the response body, not just the status code.** Several Marketing API write endpoints return `200 OK` when a request fails validation, with the problem reported in an `errors` key instead of `data`. A client that branches only on HTTP status will treat these as successes. A failed write looks like this: ```json theme={null} { "errors": ["Subject has already been taken"] } ``` Endpoints that behave this way include [creating](/marketing/campaigns/create) and [updating](/marketing/campaigns/update) a campaign mailing, [creating a subscription](/marketing/subscriptions/create), and the subscribe and unsubscribe endpoints. Treat the presence of `errors` as the failure signal, and fall back to the status code for authentication (`401`), missing records (`404`), and server errors (`500`). ### Pagination List endpoints are paginated by default. Control it with: | Parameter | Purpose | | ------------ | ---------------------------------------------------------------------- | | `items` | Records per page | | `page` | Which page to return | | `pagination` | Set to `false` to disable paging and return everything in one response | Pagination metadata is returned in the response headers. Some endpoints additionally include a `page_info` object in the response body. ### Background jobs Operations that can affect a large number of records do not run inline. Instead they queue a job and return its identifier immediately: ```json theme={null} { "data": { "jid": "a1b2c3d4e5f6a7b8c9d0e1f2" } } ``` A `jid` in the response means the work was accepted, not that it has finished. Bulk subscribe and unsubscribe across a whole list behave this way; supplying an explicit `subscriber_ids` array to those same endpoints processes the change inline and returns the affected subscribers instead. ### Dates Dates are passed as ISO8601 strings with UTC timezone. ## Finding id parameters for subscription\_list\_id, campaign\_mailing\_id, etc. ## Community & support Ask usage questions in the Paubox Community. Propose features and improvements. Never post PHI, recipient addresses, or message content in public threads. Account, billing, or anything sensitive goes to [support@paubox.com](mailto:support@paubox.com). # Campaign sends table Source: https://docs.paubox.com/marketing/analytics/campaign-sends openapi2 GET /analytics/campaign_mailing_sends_table Return json data information about campaigns sent # Campaign analytics Source: https://docs.paubox.com/marketing/analytics/campaigns openapi2 GET /analytics/campaign_mailing_send_totals Return json data statistics about campaigns sent # Campaign deliveries table Source: https://docs.paubox.com/marketing/analytics/deliveries openapi2 GET /analytics/campaign_mailing_deliveries_table Returns json data about campaign deliveries sent # Subscribers by tracking link Source: https://docs.paubox.com/marketing/analytics/subscribers-by-link openapi2 GET /analytics/subscribers_by_tracking_link Returns json data about subscribers for a specific tracking link # Tracking links by unique link Source: https://docs.paubox.com/marketing/analytics/tracking-links openapi2 GET /analytics/tracking_links_by_unique_link Returns json data about interactions with tracking links # List campaign mailings Source: https://docs.paubox.com/marketing/campaigns openapi2 GET /campaign_mailings Return json data about Paubox Marketing Campaign Emails # Bulk delete campaigns Source: https://docs.paubox.com/marketing/campaigns/bulk-delete openapi2 DELETE /campaign_mailings_bulk_delete Permanently deletes the campaign mailings identified by `campaign_mailing_ids`. There is no single-delete endpoint; delete one mailing by passing an array of one ID. This is irreversible. Deleting a mailing also removes its associated delivery and send records. Omitting `campaign_mailing_ids` is a no-op and still returns `200` with an empty object. **Example curl command:** ```bash curl -X DELETE \ https://api.paubox.com/v1/marketing/campaign_mailings_bulk_delete \ -H 'authorization: Token token=YOUR_API_KEY' \ -H 'content-type: application/json' \ -d '{ "campaign_mailing_ids": ["123e4567-e89b-12d3-a456-426614174000"] }' ``` # Create a campaign Source: https://docs.paubox.com/marketing/campaigns/create openapi2 POST /campaign_mailings Creates a campaign mailing (a marketing email) for the authenticated customer. Creating a mailing only stores the content; use [send a campaign](/marketing/campaigns/send) or [schedule a campaign](/marketing/campaigns/schedule) to deliver it. `subject` is required and must be unique within your account. An unsubscribe footer is appended to `html_part` automatically on save. **Example curl command:** ```bash curl -X POST \ https://api.paubox.com/v1/marketing/campaign_mailings \ -H 'authorization: Token token=YOUR_API_KEY' \ -H 'content-type: application/json' \ -d '{ "campaign_mailing": { "subject": "March newsletter", "html_part": "
Hello
", "text_part": "Hello", "template_type": "html" } }' ``` # Fetch a campaign Source: https://docs.paubox.com/marketing/campaigns/get openapi2 GET /campaign_mailings/{campaign_mailing_id} Returns a single campaign mailing, including its content. Unlike the list endpoint, this response carries `html_part`, `text_part` and `form_data`, and omits the aggregate delivery counts. # Schedule a campaign mailing Source: https://docs.paubox.com/marketing/campaigns/schedule openapi2 POST /campaign_mailing_schedules Schedules a new email campaign mailing with specified details for delivery at a future time # Send a campaign mailing Source: https://docs.paubox.com/marketing/campaigns/send openapi2 POST /campaign_mailing_sends Send campaign email to recipients. The email message must be created within the Paubox Marketing web interface in order to use this to trigger a send. **Note:** The campaign_mailing_id can be retrieved from the id= part of the URL when editing a marketing email. The subscription_list_id can be retrieved from the listId= part of the URL of any Contact List page. # Send a test email Source: https://docs.paubox.com/marketing/campaigns/send-test-email openapi2 GET /campaign_mailings/{campaign_mailing_id}/send_test_email Sends a one-off preview of the campaign mailing to a single address, so you can check rendering before sending to a list. The subject is prefixed with `[Test]`, and the message is sent from your account's default brand `from_email` and `from_name`. Test sends are not recorded as a campaign send and do not appear in analytics. **Example curl command:** ```bash curl -X GET \ 'https://api.paubox.com/v1/marketing/campaign_mailings/123e4567-e89b-12d3-a456-426614174000/send_test_email?to_email=me@example.com' \ -H 'authorization: Token token=YOUR_API_KEY' ``` # Update a campaign Source: https://docs.paubox.com/marketing/campaigns/update openapi2 PATCH /campaign_mailings/{campaign_mailing_id} Updates a campaign mailing. Only the fields you send are changed. `PUT` is accepted as an equivalent to `PATCH`. `subject` must stay unique within your account. The unsubscribe footer is reapplied to `html_part` on every save. **Example curl command:** ```bash curl -X PATCH \ https://api.paubox.com/v1/marketing/campaign_mailings/123e4567-e89b-12d3-a456-426614174000 \ -H 'authorization: Token token=YOUR_API_KEY' \ -H 'content-type: application/json' \ -d '{ "campaign_mailing": { "subject": "March newsletter (revised)" } }' ``` # List drip campaigns Source: https://docs.paubox.com/marketing/drip-campaigns openapi2 GET /drip_campaigns Return json data about Drip Campaigns # Get a drip campaign Source: https://docs.paubox.com/marketing/drip-campaigns/get openapi2 GET /drip_campaigns/{id} Returns json data about a drip campaign # Pause a drip campaign Source: https://docs.paubox.com/marketing/drip-campaigns/pause openapi2 POST /drip_campaigns/{id}/pause Pauses a drip campaign # Start a drip campaign Source: https://docs.paubox.com/marketing/drip-campaigns/start openapi2 POST /drip_campaigns/{id}/start Starts a drip campaign # Update a drip campaign Source: https://docs.paubox.com/marketing/drip-campaigns/update openapi2 PUT /drip_campaigns/{id} Updates drip campaign attributes # List subscription lists Source: https://docs.paubox.com/marketing/lists openapi2 GET /subscription_lists Return json data about subscription lists # Create a subscription list Source: https://docs.paubox.com/marketing/lists/create openapi2 POST /subscription_lists Creates a subscription list record # Delete a subscription list Source: https://docs.paubox.com/marketing/lists/delete openapi2 DELETE /subscription_lists/{subscription_list_id} Deletes subscription list record. **Note:** Cannot delete default/all contacts list. # Update a subscription list Source: https://docs.paubox.com/marketing/lists/update openapi2 PATCH /subscription_lists/{subscription_list_id} Updates a subscription list record # Locating parameter values Source: https://docs.paubox.com/marketing/parameter-values How to find parameter values like campaign_mailing_id, subscription_list_id, and campaign_mailing_send_id in the Paubox Marketing web dashboard. Some Marketing API endpoints require IDs that identify specific campaigns, subscription lists, or campaign sends. This page shows where to find each of those IDs in the Paubox Marketing dashboard. All IDs can also be retrieved programmatically. Campaign mailings, subscription lists, and send records all include their `id` in API responses. ## campaign\_mailing\_id A `campaign_mailing_id` uniquely identifies a marketing email (campaign mailing) in Paubox Marketing. It is required when [sending a campaign](/marketing/campaigns/send) or [scheduling a campaign](/marketing/campaigns/schedule), and is accepted as an optional filter on the [campaign deliveries table](/marketing/analytics/deliveries). **To find your `campaign_mailing_id`:** 1. Log in to the Paubox Marketing dashboard. 2. Navigate to **Marketing Emails** (Campaigns). 3. Open or edit any campaign. 4. Copy the value of the `id=` parameter from your browser's address bar. Campaign Mailing Id ## subscription\_list\_id A `subscription_list_id` uniquely identifies a subscription list. It is used when [creating subscribers](/marketing/subscribers/create), [listing subscribers](/marketing/subscribers/get), [bulk-deleting subscribers](/marketing/subscribers/bulk-delete), [sending](/marketing/campaigns/send) or [scheduling](/marketing/campaigns/schedule) a campaign to a specific list, [updating](/marketing/lists/update) or [deleting a list](/marketing/lists/delete), and [updating a drip campaign](/marketing/drip-campaigns/update). **To find your `subscription_list_id`:** 1. Log in to the Paubox Marketing dashboard. 2. Navigate to **Lists**. 3. Open or edit any list. 4. Copy the UUID from your browser's address bar. Subscription List Id 2 ## campaign\_mailing\_send\_id A `campaign_mailing_send_id` uniquely identifies a specific send of a campaign. It is required for [campaign analytics](/marketing/analytics/campaigns), [tracking links by unique link](/marketing/analytics/tracking-links), and [subscribers by tracking link](/marketing/analytics/subscribers-by-link), and is accepted as an optional filter on the [campaign deliveries table](/marketing/analytics/deliveries) and when [listing tracking links](/marketing/tracking-links). **To find your `campaign_mailing_send_id`:** 1. Log in to the Paubox Marketing dashboard. 2. Navigate to **Analytics**. 3. Click a sent campaign. 4. Copy the value of the `selectedId=` parameter from your browser's address bar. Campaign Mailing Send Id # List subscribers Source: https://docs.paubox.com/marketing/subscribers openapi2 GET /subscribers Return json data about subscribers # Bulk create subscribers Source: https://docs.paubox.com/marketing/subscribers/bulk-create openapi2 POST /subscribers_bulk_create Creates one or more subscriber records. Adds to subscription list if specified, otherwise adds to all contacts. # Bulk delete subscribers Source: https://docs.paubox.com/marketing/subscribers/bulk-delete openapi2 DELETE /subscribers_bulk_delete Deletes one or more subscriber records. Removes from subscription list if specified, otherwise removed from all contacts. **Example:** ``` DELETE /subscribers_bulk_delete?subscriber_ids=id1,id2,id3&subscription_list_id=list123 ``` # Create a subscriber Source: https://docs.paubox.com/marketing/subscribers/create openapi2 POST /subscribers Creates a subscriber record. Adds record to subscription list if specified otherwise adds to all contacts. **Example curl command:** ```bash curl -X POST \ https://api.paubox.com/v1/marketing/subscribers \ -H 'authorization: Token token=' \ -H 'content-type: application/json' \ -d '{ "subscriber": { "email": "test@example.com", "first_name": "test", "last_name": "example" } }' ``` # Get a subscriber Source: https://docs.paubox.com/marketing/subscribers/get openapi2 GET /subscribers/{subscriber_id} Return json data about subscriber # Update a subscriber Source: https://docs.paubox.com/marketing/subscribers/update openapi2 PUT /subscribers/{subscriber_id} Updates a subscriber record # List subscriptions Source: https://docs.paubox.com/marketing/subscriptions openapi2 GET /subscriptions Returns every subscription belonging to the authenticated customer. A subscription is the join between a subscriber and a subscription list (or a dynamic list), and it carries the `unsubscribed_at` timestamp that marks a list-level opt-out. This endpoint is not paginated and returns raw subscription records. # Bulk global subscribe Source: https://docs.paubox.com/marketing/subscriptions/bulk-global-subscribe openapi2 POST /subscriptions/bulk_global_subscribe Clears the global opt-out for a whole subscription list without having to enumerate every subscriber. This endpoint has two modes: - **Explicit** — supply `subscriber_ids` and the request behaves exactly like [`POST /subscriptions/subscribe`](/marketing/subscriptions/subscribe), returning the affected subscribers synchronously. - **Select-all** — omit `subscriber_ids` and supply `from_subscription_list_id`. Every subscriber matching `search` and `filters` in that list, minus `except_ids`, is queued for processing in the background and a Sidekiq job ID (`jid`) is returned immediately. In select-all mode `from_subscription_list_id` is required and must be a subscription list UUID. # Bulk global unsubscribe Source: https://docs.paubox.com/marketing/subscriptions/bulk-global-unsubscribe openapi2 POST /subscriptions/bulk_global_unsubscribe Globally opts out every subscriber in a subscription list without having to enumerate them. This endpoint has two modes: - **Explicit** — supply `subscriber_ids` and the request behaves exactly like [`POST /subscriptions/unsubscribe`](/marketing/subscriptions/unsubscribe), returning the affected subscribers synchronously. - **Select-all** — omit `subscriber_ids` and supply `from_subscription_list_id`. Every subscriber matching `search` and `filters` in that list, minus `except_ids`, is queued for processing in the background and a Sidekiq job ID (`jid`) is returned immediately. In select-all mode `from_subscription_list_id` is required and must be a subscription list UUID. Globally opting a subscriber out suppresses all future marketing email to them, across every list. # Create a subscription Source: https://docs.paubox.com/marketing/subscriptions/create openapi2 POST /subscriptions Subscribes an existing subscriber to an existing subscription list. Creating a subscription fires any drip campaign configured with a `subscription_created` trigger on that list. A subscriber may only be subscribed to a given list once; a duplicate request fails validation. **Note on `subscriber_id`:** this field takes the subscriber's internal numeric ID, not the UUID returned by the subscriber endpoints. To add a subscriber to a list by UUID, use [`POST /subscribers`](/marketing/subscribers/create) with a `subscription_list_id`, or [`POST /subscriptions/subscribe`](/marketing/subscriptions/subscribe) to re-subscribe an existing subscription. **Example curl command:** ```bash curl -X POST \ https://api.paubox.com/v1/marketing/subscriptions \ -H 'authorization: Token token=YOUR_API_KEY' \ -H 'content-type: application/json' \ -d '{ "subscription": { "subscription_list_id": "123e4567-e89b-12d3-a456-426614174000", "subscriber_id": 4821 } }' ``` # Unsubscribe a subscription Source: https://docs.paubox.com/marketing/subscriptions/delete openapi2 DELETE /subscriptions/{subscription_id} Unsubscribes the subscriber from the list this subscription belongs to by stamping `unsubscribed_at`. The subscription record itself is kept, and the subscriber stays subscribed to every other list and is not globally opted out. To re-subscribe, use [`POST /subscriptions/subscribe`](/marketing/subscriptions/subscribe). # Bulk subscribe a dynamic list Source: https://docs.paubox.com/marketing/subscriptions/dynamic-bulk-subscribe openapi2 POST /subscriptions/dynamic_bulk_subscribe The dynamic list counterpart to [bulk global subscribe](/marketing/subscriptions/bulk-global-subscribe). - **Explicit** — supply `subscriber_ids` and the request behaves exactly like [`POST /subscriptions/subscribe`](/marketing/subscriptions/subscribe). - **Select-all** — omit `subscriber_ids` and supply `from_subscription_list_id`, which here is a **dynamic list** UUID. The job resolves the list's current membership, subtracts `except_ids`, and clears the global opt-out on the rest. A Sidekiq job ID (`jid`) is returned immediately. A dynamic list's membership is defined by the filters saved on the list itself, so any `filters` sent with the request is ignored in select-all mode. # Bulk unsubscribe a dynamic list Source: https://docs.paubox.com/marketing/subscriptions/dynamic-bulk-unsubscribe openapi2 POST /subscriptions/dynamic_bulk_unsubscribe The dynamic list counterpart to [bulk global unsubscribe](/marketing/subscriptions/bulk-global-unsubscribe). - **Explicit** — supply `subscriber_ids` and the request behaves exactly like [`POST /subscriptions/unsubscribe`](/marketing/subscriptions/unsubscribe). - **Select-all** — omit `subscriber_ids` and supply `from_subscription_list_id`, which here is a **dynamic list** UUID. The job resolves the list's current membership, subtracts `except_ids`, and globally opts out the rest. A Sidekiq job ID (`jid`) is returned immediately. A dynamic list's membership is defined by the filters saved on the list itself, so any `filters` sent with the request is ignored in select-all mode. # Fetch a subscription Source: https://docs.paubox.com/marketing/subscriptions/get openapi2 GET /subscriptions/{subscription_id} Returns a single subscription by its UUID. # Subscribe subscribers Source: https://docs.paubox.com/marketing/subscriptions/subscribe openapi2 POST /subscriptions/subscribe Re-subscribes one or more subscribers, identified by their UUIDs. When `subscription_list_ids` is supplied, the matching subscriptions have their `unsubscribed_at` cleared. When it is omitted, only the global opt-out (`opted_out_on`) is cleared and list-level opt-outs are left untouched. In both cases the global opt-out is cleared, because a subscriber who is subscribed to any list is by definition not globally unsubscribed. **Example curl command:** ```bash curl -X POST \ https://api.paubox.com/v1/marketing/subscriptions/subscribe \ -H 'authorization: Token token=YOUR_API_KEY' \ -H 'content-type: application/json' \ -d '{ "subscriber_ids": ["8f14e45f-ceea-467a-9f2c-4b3d2a1e5c60"], "subscription_list_ids": ["123e4567-e89b-12d3-a456-426614174000"] }' ``` # Unsubscribe subscribers Source: https://docs.paubox.com/marketing/subscriptions/unsubscribe openapi2 POST /subscriptions/unsubscribe Unsubscribes one or more subscribers, identified by their UUIDs. When `subscription_list_ids` is supplied, the matching subscriptions are stamped with `unsubscribed_at` and the subscribers remain subscribed to every other list. When it is omitted, the subscribers are globally opted out by stamping `opted_out_on`, which suppresses all future marketing email to them. **Example curl command:** ```bash curl -X POST \ https://api.paubox.com/v1/marketing/subscriptions/unsubscribe \ -H 'authorization: Token token=YOUR_API_KEY' \ -H 'content-type: application/json' \ -d '{ "subscriber_ids": ["8f14e45f-ceea-467a-9f2c-4b3d2a1e5c60"], "subscription_list_ids": ["123e4567-e89b-12d3-a456-426614174000"] }' ``` # List tracking links Source: https://docs.paubox.com/marketing/tracking-links openapi2 GET /tracking_links Return a list of json data for all tracking links associated to a campaign mailing send or campaign mailing delivery. **Note:** The campaign_mailing_send_id is available from the Paubox Marketing interface analytics page. Click a sent campaign and the URL will show a selectedId. If you pass in the campaign_mailing_send_id, it will return a list of every link in every email sent in the campaign and the tracking info. # Authentication Source: https://docs.paubox.com/mcp-server/authentication How to obtain and pass credentials to the Paubox MCP Server. The Paubox MCP Server authenticates against the Paubox Email API on your behalf. You provide your API key once when configuring the server; it is never sent to the AI model. If you don't have one, [sign up for Paubox Email API](https://www.paubox.com/pricing/paubox-email-api). Free tier includes up to 300 emails per month. Go to [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings) and add the domain you will send from. Complete domain verification before generating an API key; unverified domains cannot send email. From [Paubox Email API > Settings](https://next.paubox.com/emailapi/settings), click your domain, then press **Add API Key**. Give the key a description and save it immediately; it is displayed only once. The server resolves the API key using the following priority order. Use whichever method fits your client: **1. OAuth connector form (recommended for Claude.ai and Claude Desktop)** Add `https://mcp.paubox.com` as a connector. The client presents a **Configure Paubox** form automatically. Enter your API key once; the client stores a short-lived encrypted Bearer token and sends it on every subsequent request. **2. Custom request headers** Some MCP clients let you set custom headers on the connector. Pass: ``` x-paubox-api-key: your_api_key ``` **3. Environment variables (stdio / Claude Code)** ```bash theme={null} export PAUBOX_API_KEY=your_api_key ``` Claude Code / stdio config: ```json theme={null} { "mcpServers": { "paubox": { "command": "npx", "args": ["-y", "@paubox/mcp@latest"], "env": { "PAUBOX_API_KEY": "your_api_key" } } } } ``` **4. Per-call tool parameters (any transport)** Pass `apiKey` directly in each tool call. This takes the highest runtime priority and overrides any other credential source. Useful for scripted clients or one-off requests. Never commit API keys to source control. Use environment variables or a secrets manager to inject credentials at runtime. ## Required permissions | Tool | Credential required | | :--------------------- | :------------------------------------ | | `send_secure_email` | Paubox Email API key with send access | | `check_email_status` | Paubox Email API key with read access | | `validate_credentials` | Any valid Paubox Email API key | | `get_form` | None (public endpoint) | | `submit_form` | None (public endpoint) | A single API key generated from the Paubox Email API settings page covers all authenticated tools. The `get_form` and `submit_form` tools call public Forms API endpoints and require no credentials. # Paubox MCP Server Source: https://docs.paubox.com/mcp-server/index The Paubox MCP Server gives AI assistants HIPAA compliant email and secure form tools through the Model Context Protocol. The Paubox MCP Server lets AI assistants send HIPAA compliant email and work with Paubox Forms by calling Paubox tools through the Model Context Protocol (MCP). Once connected, a client like Claude, Claude Desktop, Cursor, or any other MCP-compatible host can send encrypted email, check delivery status, fetch Paubox Form definitions, and submit form responses. The server runs the Paubox Email API and Paubox Forms calls on your behalf. Your API key stays with the server process and is never shared with the AI model. ## What you can build with it Healthcare teams use the Paubox MCP Server to: * Draft and send encrypted appointment reminders, follow-ups, and care instructions from inside a clinical AI assistant * Run an intake conversation that fetches a Paubox Form, collects answers, and submits the response without copying data between tools * Add email delivery and status checks to multi-step agent workflows that span scheduling, billing, or patient outreach ## Available tools | Tool | What it does | | :--------------------- | :-------------------------------------------------------- | | `send_secure_email` | Send a HIPAA compliant email through the Paubox Email API | | `check_email_status` | Check delivery status for a previously sent message | | `validate_credentials` | Verify that the configured API key is valid | | `get_form` | Retrieve metadata for a Paubox Form | | `submit_form` | Submit a response to a Paubox Form | See [MCP tools](/mcp-server/tools) for the full parameter reference for each tool ## Compatible clients Any client that follows the MCP specification can connect to the Paubox MCP Server, including: * Claude (via Settings > Integrations on [claude.ai](http://claude.ai)) * Claude Desktop * Cursor, Windsurf, and other MCP-aware IDEs * Custom agents and scripts built on the MCP specification The server supports two transports, so clients can pick whichever they natively use: * HTTP at `https://mcp.paubox.com`: authenticate via the OAuth connector form, or set the `x-paubox-api-key` header * stdio via `npx @paubox/mcp@latest` with `PAUBOX_API_KEY` in the environment ## How it handles HIPAA and credentials Email sent through the Paubox MCP Server is encrypted by the Paubox Email API, the same HIPAA compliant transactional email service that Paubox provides to healthcare developers. Messages count against your Email API plan and appear in your Paubox dashboard alongside the rest of your API traffic. Your API key is held by the MCP server process. It is not sent to the AI model and does not appear in tool calls or model context. ## Get started Connect Claude, Claude Desktop, or any MCP client. See parameters and responses for every tool. Generate a Paubox Email API key and configure credentials. Learn more about the underlying Paubox Email API ## FAQs It is a Model Context Protocol server hosted at `https://mcp.paubox.com` that gives AI assistants access to the Paubox Email API and Paubox Forms. Agents can send HIPAA compliant email and submit secure intake forms as native tool calls. Yes. Connect through Settings > Integrations on claude.ai, or add the server to `claude_desktop_config.json` for Claude Desktop. The [Quickstart](/mcp-server/quickstart) covers both setups. No. It uses your existing [Paubox Email API](https://www.paubox.com/products/paubox-email-api) account. The Email API free tier covers 300 emails per month.  No. The key is held by the MCP server process. The assistant calls tools by name and never sees the credentials. HTTP at `https://mcp.paubox.com`, and stdio via the `@paubox/mcp` npm package. ## Community & support Ask usage questions in the Paubox Community. Propose features and improvements. Never post PHI, recipient addresses, or message content in public threads. Account, billing, or anything sensitive goes to [support@paubox.com](mailto:support@paubox.com). # Quickstart Source: https://docs.paubox.com/mcp-server/quickstart Connect the Paubox MCP Server to Claude, Claude Desktop, Claude Code, or any MCP-compatible client. **HIPAA Notice:** If you plan to send or reference PHI when using the Paubox MCP connector through Claude, you must have a Business Associate Agreement (BAA) in place with Anthropic. BAAs are available on Claude Team and Enterprise plans. Your Paubox subscription covers email delivery, it does not cover protected health information (PHI) processed within Claude's context window. Before connecting, you need a Paubox Email API key. See [Authentication](/mcp-server/authentication) for how to generate one from the Paubox dashboard. 1. In [Claude.ai](https://claude.ai), open **Settings > Integrations**. 2. Click **Add connector** and enter the server URL: ``` https://mcp.paubox.com ``` 3. A **Configure Paubox** form opens automatically. Enter your API key. 4. Click **Save**. Claude stores your API key as a secure token and sends it on every request. Claude will discover the available Paubox tools automatically. Open (or create) `claude_desktop_config.json` and add the `paubox` entry to `mcpServers`: ```json claude_desktop_config.json (HTTP, recommended) theme={null} { "mcpServers": { "paubox": { "url": "https://mcp.paubox.com" } } } ``` ```json claude_desktop_config.json (stdio) theme={null} { "mcpServers": { "paubox": { "command": "npx", "args": ["-y", "@paubox/mcp@latest"], "env": { "PAUBOX_API_KEY": "YOUR_API_KEY" } } } } ``` The config file is located at: * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` When using the HTTP config, Claude Desktop will open a browser window to the Configure Paubox form to collect your credentials. Restart Claude Desktop after saving. Run this command once with your API key (no credential prompts afterward): ```bash theme={null} claude mcp add paubox \ -e PAUBOX_API_KEY=your_api_key \ -- npx @paubox/mcp@latest ``` Claude Code spawns the package locally and injects the API key as an environment variable. Ask Claude to verify your credentials: > "Use the Paubox MCP server to validate my credentials." Claude will call `validate_credentials` and confirm your API key is working. Then try sending a test email: > "Send a test email to [me@example.com](mailto:me@example.com) from [sender@yourdomain.com](mailto:sender@yourdomain.com) with the subject 'Hello from MCP'." Claude will call `send_secure_email` and return a `sourceTrackingId` you can use to check delivery status. ## Other MCP-compatible clients Any client that supports the MCP specification can connect using either transport: * **HTTP**: `https://mcp.paubox.com`; use OAuth (credential form in the client UI) or set `x-paubox-api-key` as a custom request header * **stdio**: run `npx @paubox/mcp@latest` with `PAUBOX_API_KEY` set in the environment # MCP tools Source: https://docs.paubox.com/mcp-server/tools Reference for all tools exposed by the Paubox MCP Server. The Paubox MCP Server exposes five tools. Each tool maps to an underlying Paubox API operation and is available to any connected MCP client. ## send\_secure\_email Sends a single HIPAA compliant email through the Paubox Email API. The sender address must belong to a domain you have verified in the Paubox dashboard. | Parameter | Type | Required | Description | | :------------------------ | :--------------- | :------- | :---------------------------------------------------------------------------------------------------- | | `from` | string | Yes | Sender address. Must be on a verified Paubox domain. | | `to` | array of strings | Yes | Recipient email addresses. | | `subject` | string | Yes | Email subject line. | | `message` | string | Yes | Message body. Sent as plain text; the server also generates an HTML version automatically. | | `cc` | array of strings | No | CC recipients. | | `bcc` | array of strings | No | BCC recipients. | | `forceSecureNotification` | boolean | No | Force a secure notification regardless of recipient settings. Defaults to `false`. | | `apiKey` | string | No | Paubox API key. HTTP transport only; overrides the credential resolved from the connector or headers. | **Example payload** ```json theme={null} { "from": "provider@clinic.com", "to": ["patient@example.com"], "subject": "Your appointment summary", "message": "Thank you for visiting us today." } ``` **Response:** returns a `sourceTrackingId` string you can pass to `check_email_status`. ## check\_email\_status Retrieves the current delivery status of a message sent via `send_secure_email`. | Parameter | Type | Required | Description | | :----------------- | :----- | :------- | :----------------------------------------------- | | `sourceTrackingId` | string | Yes | The tracking ID returned by `send_secure_email`. | | `apiKey` | string | No | Paubox API key. HTTP transport only. | **Example payload** ```json theme={null} { "sourceTrackingId": "abc123def456" } ``` **Response:** returns an object with delivery disposition details and a timestamp. ## validate\_credentials Verifies that the Paubox API credentials are present and valid by making a live check against the Paubox API. Useful as a first step before sending email. | Parameter | Type | Required | Description | | :-------- | :----- | :------- | :--------------------------------------------------------------------------------------------------------------------------- | | `apiKey` | string | No | Paubox API key. HTTP transport only; pass if you want to validate a specific API key rather than the one already configured. | When connecting via stdio (Claude Code), the API key comes from an environment variable and no parameters are needed. **Example payload** ```json theme={null} {} ``` **Response:** returns a confirmation with a masked API key on success, or an error description if the API key is missing or invalid. ## get\_form Available via HTTP transport only. Not available when using the stdio transport (Claude Code). Retrieves the full definition of a Paubox Form, including its title, description, and field schema, so an agent can present the form questions in a conversation. No authentication is required for this tool. | Parameter | Type | Required | Description | | :-------- | :------------ | :------- | :----------------------------------- | | `formId` | string (UUID) | Yes | UUID of the Paubox Form to retrieve. | **Example payload** ```json theme={null} { "formId": "550e8400-e29b-41d4-a716-446655440000" } ``` **Response:** returns a form object including `title`, `description`, `form_json` (field definitions), and metadata fields (`active`, `signable`, `submission_count`, `created_at`, `updated_at`). ## submit\_form Available via HTTP transport only. Not available when using the stdio transport (Claude Code). Submits a completed response to a Paubox Form. No authentication is required for this tool. | Parameter | Type | Required | Description | | :------------ | :--------------- | :------- | :------------------------------------------------------------------------------------------------------------------------ | | `formId` | string (UUID) | Yes | UUID of the form being submitted. | | `formData` | object | Yes | Key-value pairs matching the form's field schema (`form_json`). Structure varies per form. | | `attachments` | array of objects | No | File attachments. Each object must include `name` (filename) and `content` (base64-encoded file). Max total size: 250 MB. | **Example payload: text fields only** ```json theme={null} { "formId": "550e8400-e29b-41d4-a716-446655440000", "formData": { "first_name": "Jane", "last_name": "Smith", "email": "jane@example.com" } } ``` **Example payload: with attachment** ```json theme={null} { "formId": "550e8400-e29b-41d4-a716-446655440000", "formData": { "first_name": "Jane" }, "attachments": [ { "name": "consent.pdf", "content": "JVBERi0xLjQ..." } ] } ``` **Response:** returns a success confirmation message on success. # Authentication Source: https://docs.paubox.com/node-sdk/authentication Configure your Paubox API credentials for the Node.js SDK. ## Obtain your credentials The Email API service requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Option 1: Environment variables (recommended) Create a `.env` file in your project root: ```bash theme={null} API_KEY=YOUR_API_KEY ``` The SDK loads this automatically via `dotenv`. Call the factory with no arguments: ```javascript theme={null} const pbMail = require('paubox-node'); const service = pbMail.emailService(); ``` ## Option 2: Direct configuration Pass the API key explicitly to the factory: ```javascript theme={null} const service = pbMail.emailService({ apiKey: 'YOUR_API_KEY' }); ``` The service factory throws immediately if the API key is missing, so errors surface at startup rather than at send time. The SDK sets the `Authorization` header automatically on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Forms service `formService` requires no credentials: ```javascript theme={null} const formsService = pbMail.formService(); ``` ## Security notes * Add `.env` to your `.gitignore`; never commit credentials to source control. * In production, prefer your platform's secret management (environment variables injected by your hosting provider) over `.env` files. # Email client Source: https://docs.paubox.com/node-sdk/email-client Full reference for the Paubox Node.js SDK email service: building messages, sending email, tracking delivery, dynamic templates, and error handling. ## Create the service ```javascript theme={null} const pbMail = require('paubox-node'); // Read the API key from environment variables const service = pbMail.emailService(); // Or pass the API key directly const service = pbMail.emailService({ apiKey: 'YOUR_API_KEY' }); ``` ## Build a message ```javascript theme={null} const message = pbMail.message({ from: 'sender@yourdomain.com', // required to: ['alice@example.com'], // required; array of recipients subject: 'Your results are ready', // optional reply_to: 'support@yourdomain.com', // optional cc: ['manager@example.com'], // optional bcc: ['audit@example.com'], // optional text_content: 'Plain text body.', // at least one of text/html required html_content: '

HTML body.

', // optional; auto base64-encoded allowNonTLS: false, // optional; default false forceSecureNotification: false, // optional; default false custom_headers: { 'X-Custom': 'value' },// optional; keys must start with X- list_unsubscribe: '', // optional list_unsubscribe_post: 'List-Unsubscribe=One-Click', // optional attachments: [attachment] // optional; see below }); ``` ### Attachments ```javascript theme={null} const fs = require('fs'); const attachment = { fileName: 'report.pdf', contentType: 'application/pdf', content: fs.readFileSync('report.pdf').toString('base64') }; ``` ## Send a message ```javascript theme={null} const response = await service.sendMessage(message); console.log('Tracking ID:', response.sourceTrackingId); ``` ## Send bulk messages Send up to 50 messages in a single API call. Each message gets its own tracking ID. ```javascript theme={null} const messages = [ pbMail.message({ from: 'f@yourdomain.com', to: ['alice@example.com'], subject: 'Hi Alice', text_content: 'Hello Alice' }), pbMail.message({ from: 'f@yourdomain.com', to: ['bob@example.com'], subject: 'Hi Bob', text_content: 'Hello Bob' }), ]; const response = await service.sendBulkMessages(messages); for (const msg of response.messages) { console.log('Tracking ID:', msg.sourceTrackingId); } ``` ## Check delivery status ```javascript theme={null} const disposition = await service.getEmailDisposition(sourceTrackingId); for (const delivery of disposition.data.message.message_deliveries) { console.log(`${delivery.recipient} → ${delivery.status.deliveryStatus}`); } ``` Common `deliveryStatus` values: `delivered`, `opened`, `failed`, `pending`. The `openedStatus` field defaults to `"unopened"` if not yet set. ## Dynamic templates Templates use [Handlebars](https://handlebarsjs.com) syntax (`{{variable_name}}`). Template content can be a string, `Buffer`, or readable `Stream`. ### Create a template ```javascript theme={null} const fs = require('fs'); const response = await service.createDynamicTemplate( 'appointment-confirmation', fs.createReadStream('templates/appointment.html') ); ``` ### List templates ```javascript theme={null} const templates = await service.listDynamicTemplates(); for (const t of templates) { console.log(t.id, t.name); } ``` ### Get a template ```javascript theme={null} const template = await service.getDynamicTemplate(templateId); ``` ### Update a template Pass `null` for fields you want to leave unchanged: ```javascript theme={null} await service.updateDynamicTemplate( templateId, 'appointment-confirmation-v2', // new name, or null to keep current null // content unchanged ); ``` ### Delete a template ```javascript theme={null} await service.deleteDynamicTemplate(templateId); ``` ### Send a templated message ```javascript theme={null} const message = pbMail.templatedMessage({ from: 'appointments@yourclinic.com', to: ['jane@example.com'], subject: 'Your appointment is confirmed', template_name: 'appointment-confirmation', template_values: { first_name: 'Jane', date: '2024-03-15', time: '2:00 PM' } }); const response = await service.sendTemplatedMessage(message); ``` ## Error handling All service methods return Promises and reject on API errors. Use `try/catch` in async functions: ```javascript theme={null} try { const response = await service.sendMessage(message); console.log('Sent:', response.sourceTrackingId); } catch (err) { console.error('Send failed:', err.message); } ``` # Forms client Source: https://docs.paubox.com/node-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Node.js SDK. The Forms service does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Create the service ```javascript theme={null} const pbMail = require('paubox-node'); const formsService = pbMail.formService(); ``` ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```javascript theme={null} const form = await formsService.getForm('your-form-uuid'); console.log('Title:', form.title); console.log('HTML:', form.form_html); ``` Key fields in the response: | Field | Description | | ------------------ | ----------------------------------------- | | `title` | Display name of the form | | `form_json` | Parsed field schema | | `form_html` | Rendered HTML for embedding | | `form_css` | Associated stylesheet | | `active` | Whether the form is accepting submissions | | `submission_count` | Number of submissions received | The Promise rejects with a 404 error if the form UUID is invalid. ## Submit a form ```javascript theme={null} await formsService.submitForm('your-form-uuid', { first_name: 'Jane', last_name: 'Smith', email: 'jane@example.com' }); // Resolves to null on success (HTTP 201) ``` ### Submitting with file attachments The maximum total request size is 250 MB. ```javascript theme={null} const fs = require('fs'); const attachments = [ { name: 'consent.pdf', content: fs.readFileSync('consent.pdf').toString('base64') } ]; await formsService.submitForm( 'your-form-uuid', { first_name: 'Jane' }, attachments ); ``` Each attachment is an object with two fields: | Field | Description | | --------- | ----------------------------- | | `name` | File name including extension | | `content` | Base64-encoded file content | ## Error handling ```javascript theme={null} try { const form = await formsService.getForm('your-form-uuid'); await formsService.submitForm('your-form-uuid', formData); } catch (err) { console.error('Forms error:', err.message); } ``` The Promise rejects on HTTP 400 (bad request) and 404 (form not found). # Paubox Node.js SDK Source: https://docs.paubox.com/node-sdk/index Send HIPAA compliant email and submit Paubox Forms from Node.js applications using the official Paubox Node.js SDK. The Paubox Node.js SDK is the official JavaScript client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). All service methods return Promises, making them compatible with `async/await` and promise chains. ```bash theme={null} npm install --save paubox-node ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Send batches of up to 50 messages in a single API call * Create, update, and delete dynamic Handlebars templates * Send templated messages with per-message variable substitution * Track delivery status and engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * Node.js v22 or later ## Get started Install, configure credentials, and send your first email in minutes. Set up your API key via environment variables or direct config. Full reference for sending email, managing templates, and tracking delivery. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/node-sdk/quickstart Install the Paubox Node.js SDK and send your first HIPAA compliant email in minutes. ```bash theme={null} npm install --save paubox-node ``` Create a `.env` file in your project root: ```bash theme={null} API_KEY=YOUR_API_KEY ``` The SDK loads this automatically. To obtain your API key, see [Authentication](/node-sdk/authentication). Create `send.js`: ```javascript theme={null} const pbMail = require('paubox-node'); async function main() { const service = pbMail.emailService(); const message = pbMail.message({ from: 'sender@yourdomain.com', to: ['recipient@example.com'], subject: 'Your first Paubox email', text_content: 'This message was sent with the Paubox Node.js SDK.' }); const response = await service.sendMessage(message); console.log('Sent. Tracking ID:', response.sourceTrackingId); } main().catch(console.error); ``` Run it: ```bash theme={null} node send.js ``` ``` Sent. Tracking ID: abc123-def456-... ``` ```javascript theme={null} const disposition = await service.getEmailDisposition(response.sourceTrackingId); for (const delivery of disposition.data.message.message_deliveries) { console.log(`${delivery.recipient} → ${delivery.status.deliveryStatus}`); } ``` ``` recipient@example.com → delivered ``` ## Next steps * [Email client](/node-sdk/email-client): bulk send, attachments, dynamic templates, error handling * [Forms client](/node-sdk/forms-client): retrieve form schemas and submit responses # Authentication Source: https://docs.paubox.com/perl-sdk/authentication Configure your Paubox API credentials for the Perl SDK. ## Obtain your API key The Email API client requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Create a config.cfg file Create a `config.cfg` file in your project directory: ``` API_KEY = YOUR_API_KEY ``` The SDK reads this file automatically via `Config::General` when you instantiate the client; no constructor arguments are needed: ```perl theme={null} my $service = Paubox_Email_SDK->new(); ``` The constructor dies immediately if `API_KEY` is missing from the config file, so credential errors are caught at startup. The SDK sets the `Authorization` header on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Forms client `Paubox_Forms_SDK` requires no credentials. Form submissions are associated with the form ID, not an API key. ```perl theme={null} my $forms = Paubox_Forms_SDK->new(); ``` ## Security notes * Do not commit `config.cfg` to source control. Add it to your `.gitignore`. * The SDK does not expose credentials in error messages or logs. # Email client Source: https://docs.paubox.com/perl-sdk/email-client Full reference for the Paubox Perl SDK Email API client: building messages, sending email, tracking delivery, and error handling. ## Instantiation ```perl theme={null} my $service = Paubox_Email_SDK->new(); ``` Credentials are read automatically from `config.cfg` (see [Authentication](/perl-sdk/authentication)). The constructor dies if `API_KEY` is missing. ## Building a message Messages are created with a single constructor call using named parameters: ```perl theme={null} my $msg = Paubox_Email_SDK::Message->new( 'from' => 'sender@yourdomain.com', # required 'to' => ['alice@example.com'], # required; arrayref 'subject' => 'Your results are ready', # required 'text_content' => 'Plain text body.', # at least one required 'html_content' => '

HTML body.

', # optional; auto base64-encoded 'replyTo' => 'support@yourdomain.com', # optional 'cc' => ['manager@example.com'], # optional; arrayref 'bcc' => ['audit@example.com'], # optional; arrayref 'allowNonTLS' => 0, # optional; default 0 'forceSecureNotification' => 'true', # optional; "true"/"false" 'attachments' => [$attachment], # optional; arrayref of hashrefs ); ``` ### Attachments Each attachment is a hashref with three keys. Use `MIME::Base64` to encode the file content: ```perl theme={null} use MIME::Base64; use String::Util qw(trim); my $attachment = { fileName => 'report.pdf', contentType => 'application/pdf', content => trim(encode_base64(do { local $/; open my $fh, '<:raw', 'report.pdf' or die $!; <$fh> })), }; my $msg = Paubox_Email_SDK::Message->new( 'from' => 'sender@yourdomain.com', 'to' => ['alice@example.com'], 'subject' => 'Report attached', 'text_content'=> 'See attachment.', 'attachments' => [$attachment], ); ``` `String::Util::trim` removes the trailing newline that `encode_base64` appends. ## Send a message ```perl theme={null} my $response = $service->sendMessage($msg); ``` The response is a raw JSON string. Decode it to access fields: ```perl theme={null} use JSON; my $json = decode_json($response); print "Tracking ID: " . $json->{sourceTrackingId} . "\n"; ``` ## Check delivery status ```perl theme={null} my $disposition = $service->getEmailDisposition($sourceTrackingId); my $json = decode_json($disposition); for my $delivery (@{ $json->{data}{message}{message_deliveries} }) { print $delivery->{recipient} . " -> " . $delivery->{status}{deliveryStatus} . "\n"; } ``` Common `deliveryStatus` values: `delivered`, `opened`, `failed`, `pending`. ## Error handling Both `sendMessage` and `getEmailDisposition` use `TryCatch` internally and die on API errors. Wrap calls in an `eval` block to catch failures: ```perl theme={null} eval { my $response = $service->sendMessage($msg); print "Sent: $response\n"; }; if ($@) { print "Error: $@\n"; } ``` # Forms client Source: https://docs.paubox.com/perl-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Perl SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Instantiation ```perl theme={null} use Paubox_Forms_SDK; my $forms = Paubox_Forms_SDK->new(); ``` ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```perl theme={null} my $response = $forms->getForm("your-form-uuid"); ``` The response is a raw JSON string. Decode it to access fields: ```perl theme={null} use JSON; my $form = decode_json($response); print "Title: " . $form->{title} . "\n"; print "HTML: " . $form->{form_html} . "\n"; ``` Key fields in the response: | Field | Description | | ----------- | ----------------------------------------- | | `title` | Display name of the form | | `form_json` | Parsed field schema | | `form_html` | Rendered HTML for embedding | | `form_css` | Associated stylesheet | | `active` | Whether the form is accepting submissions | `getForm` dies if the form UUID is invalid or the form is not found. ## Submit a form ```perl theme={null} my $result = $forms->submitForm( "your-form-uuid", { first_name => "Jane", last_name => "Smith", email => "jane@example.com" } ); # $result is an empty string on success (HTTP 201) ``` `submitForm` dies on failure with a message containing the HTTP status code and response body. ### Submitting with file attachments The maximum total request size is 250 MB. ```perl theme={null} use MIME::Base64; open(my $fh, '<:raw', 'consent.pdf') or die "Cannot open file: $!"; local $/; my $encoded = encode_base64(<$fh>); close($fh); my $result = $forms->submitForm( "your-form-uuid", { first_name => "Jane" }, [ { name => "consent.pdf", content => $encoded } ] ); ``` Each attachment in the arrayref is a hashref with two keys: | Key | Description | | --------- | ----------------------------- | | `name` | File name including extension | | `content` | Base64-encoded file content | ## Error handling Wrap calls in `eval` to catch failures: ```perl theme={null} eval { my $form = $forms->getForm("your-form-uuid"); my $result = $forms->submitForm("your-form-uuid", \%formData); }; if ($@) { print "Error: $@\n"; } ``` # Paubox Perl SDK Source: https://docs.paubox.com/perl-sdk/index Send HIPAA compliant email and submit Paubox Forms from Perl applications using the official Paubox Perl SDK. The Paubox Perl SDK is the official Perl client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). Install it from CPAN and send encrypted, HIPAA compliant email with a few lines of Perl. ```bash theme={null} cpanm Paubox_Email_SDK ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Add CC, BCC, attachments, and HTML or plain-text content * Track delivery status and engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * Perl * [cpanminus](https://metacpan.org/pod/App::cpanminus) (`cpanm`) * Dependencies installed automatically: `JSON`, `Config::General`, `REST::Client`, `TryCatch`, `String::Util`, `MIME::Base64` ## Get started Install, configure credentials, and send your first email in minutes. Set up your API key via a config.cfg file. Full reference for sending email and checking delivery status. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/perl-sdk/quickstart Install the Paubox Perl SDK and send your first HIPAA compliant email in minutes. Install from CPAN using cpanminus: ```bash theme={null} cpanm Paubox_Email_SDK ``` Create a `config.cfg` file in your project directory: ``` API_KEY = YOUR_API_KEY ``` The SDK reads this file automatically. To obtain credentials, see [Authentication](/perl-sdk/authentication). Create a file `send.pl`: ```perl theme={null} use Paubox_Email_SDK; my $msg = Paubox_Email_SDK::Message->new( 'from' => 'sender@yourdomain.com', 'to' => ['recipient@example.com'], 'subject' => 'Your first Paubox email', 'text_content' => 'This message was sent with the Paubox Perl SDK.', ); my $service = Paubox_Email_SDK->new(); my $response = $service->sendMessage($msg); print "Response: $response\n"; ``` Run it: ```bash theme={null} perl send.pl ``` The response is a JSON string containing `sourceTrackingId`: ```json theme={null} {"sourceTrackingId":"abc123-def456-...","data":{"message":{"id":"..."}}} ``` Pass the tracking ID to `getEmailDisposition`: ```perl theme={null} use JSON; my $json = decode_json($response); my $tracking = $json->{sourceTrackingId}; my $disposition = $service->getEmailDisposition($tracking); print "Disposition: $disposition\n"; ``` The response JSON includes `message_deliveries` with per-recipient status. ## Next steps * [Email client](/perl-sdk/email-client): add HTML, attachments, CC/BCC, and handle errors * [Forms client](/perl-sdk/forms-client): retrieve form schemas and submit responses # Authentication Source: https://docs.paubox.com/php-sdk/authentication Configure your Paubox API key for the PHP SDK. ## Obtain your API key The Email API client requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Create a .env file Add your API key to a `.env` file at your project root: ```bash theme={null} PAUBOX_API_KEY=YOUR_API_KEY ``` The SDK reads these variables automatically on instantiation; no constructor arguments are needed: ```php theme={null} $paubox = new Paubox\Paubox(); ``` The SDK sets the `Authorization` header on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Forms client `Paubox\PauboxForms` requires no credentials. Form submissions are associated with the form ID, not an API key. ```php theme={null} $forms = new Paubox\PauboxForms(); ``` ## Security notes * Never commit `.env` files to source control. Add `.env` to your `.gitignore`. * The SDK does not expose credentials in error messages or logs. # Email client Source: https://docs.paubox.com/php-sdk/email-client Full reference for the Paubox PHP SDK Email API client: building messages, sending email, tracking delivery, and error handling. ## Instantiation ```php theme={null} $paubox = new Paubox\Paubox(); ``` Your API key is read automatically from the `.env` file (see [Authentication](/php-sdk/authentication)). No constructor arguments are needed. ## Building a message A message is assembled from three objects (`Header`, `Content`, and `Message`) plus optional `Attachment` objects. ### Header ```php theme={null} $header = new Paubox\Mail\Header(); $header->setSubject("Your results are ready"); // required $header->setFrom("sender@yourdomain.com"); // required $header->setReplyTo("support@yourdomain.com"); // optional ``` ### Content ```php theme={null} $content = new Paubox\Mail\Content(); $content->setPlainText("Plain text body."); // at least one required $content->setHtmlText("

HTML body.

"); // optional; auto base64-encoded by SDK ``` ### Message ```php theme={null} $message = new Paubox\Mail\Message(); $message->setHeader($header); // required $message->setContent($content); // required $message->setRecipients(["alice@example.com"]); // required $message->setCc(["manager@example.com"]); // optional $message->setBcc(["audit@example.com"]); // optional $message->setAttachments([$attachment]); // optional; see below $message->setAllowNonTLS(false); // optional; default false $message->setForceSecureNotification("true"); // optional ``` ### Attachments ```php theme={null} $attachment = new Paubox\Mail\Attachment(); $attachment->setFileName("report.pdf"); $attachment->setContentType("application/pdf"); $attachment->setContent(base64_encode(file_get_contents("/path/to/report.pdf"))); $message->setAttachments([$attachment]); ``` ## Send a message ```php theme={null} $response = $paubox->sendMessage($message); ``` The response is a `stdClass` object: | Property | Description | | ------------------ | ---------------------------------------- | | `sourceTrackingId` | Tracking ID for checking delivery status | | `data` | Raw response data | | `errors` | Array of error strings, if any | ```php theme={null} echo $response->sourceTrackingId; ``` ## Check delivery status ```php theme={null} $disposition = $paubox->getEmailDisposition($sourceTrackingId); ``` `$disposition->data->message->message_deliveries` is an array of per-recipient objects: ```php theme={null} foreach ($disposition->data->message->message_deliveries as $delivery) { echo $delivery->recipient . " → " . $delivery->status->deliveryStatus . PHP_EOL; } ``` Common `deliveryStatus` values: `delivered`, `opened`, `failed`, `pending`. ## Error handling Both `sendMessage` and `getEmailDisposition` throw `\Exception` on failure. Wrap calls in a try/catch block: ```php theme={null} try { $response = $paubox->sendMessage($message); echo "Sent. Tracking ID: " . $response->sourceTrackingId . PHP_EOL; } catch (\Exception $e) { echo "Error: " . $e->getMessage() . PHP_EOL; } ``` `sendMessage` throws if the `Header` or `Content` is null, or if the API response cannot be parsed. `getEmailDisposition` throws if the response cannot be parsed. # Forms client Source: https://docs.paubox.com/php-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the PHP SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Instantiation ```php theme={null} $forms = new Paubox\PauboxForms(); ``` ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```php theme={null} $form = $forms->getForm("your-form-uuid"); echo $form->title . PHP_EOL; echo $form->form_html . PHP_EOL; print_r($form->form_json); ``` The returned `stdClass` includes: | Property | Description | | ------------------ | ----------------------------------------- | | `title` | Display name of the form | | `form_json` | Parsed field schema | | `form_html` | Rendered HTML for embedding | | `form_css` | Associated stylesheet | | `active` | Whether the form is accepting submissions | | `submission_count` | Number of submissions received | `getForm` throws `\Exception` if the form is not found or the response is invalid. ## Submit a form ```php theme={null} $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 ``` `submitForm` returns `true` on a successful submission (HTTP 201) and throws `\Exception` on failure with a message containing the HTTP status code and response body. ### Submitting with file attachments The maximum total request size is 250 MB. ```php theme={null} $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"]); $submission->setAttachments([$attachment]); $result = $forms->submitForm("your-form-uuid", $submission); ``` The `FormAttachment` fields: | Field | Description | | --------- | ----------------------------- | | `name` | File name including extension | | `content` | Base64-encoded file content | ## Error handling Wrap calls in a try/catch block: ```php theme={null} try { $form = $forms->getForm("your-form-uuid"); $result = $forms->submitForm("your-form-uuid", $submission); } catch (\Exception $e) { echo "Error: " . $e->getMessage() . PHP_EOL; } ``` # Paubox PHP SDK Source: https://docs.paubox.com/php-sdk/index Send HIPAA compliant email and submit Paubox Forms from PHP applications using the official Paubox PHP SDK. The Paubox PHP SDK is the official PHP client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). Install it with Composer and send encrypted, HIPAA compliant email with a few lines of PHP. ```bash theme={null} composer require paubox/paubox-php ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Add CC, BCC, attachments, and HTML or plain-text content * Track delivery status and engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * PHP (any recent version) * [Composer](https://getcomposer.org) * Dependencies installed automatically: `nategood/httpful` and `vlucas/phpdotenv` ## Get started Install, configure your API key, and send your first email in minutes. Set up your API key via a .env file. Full reference for sending email and checking delivery status. Retrieve form schemas and submit responses. ## Version note The current stable release (v1) covers sending email and the Forms API. A v2 beta on the `sdk-generation/v2.0.0-beta` branch adds bulk sending and dynamic templates. This documentation covers v1. # Quickstart Source: https://docs.paubox.com/php-sdk/quickstart Install the Paubox PHP SDK and send your first HIPAA compliant email in minutes. Add the SDK to your project with Composer: ```bash theme={null} composer require paubox/paubox-php ``` Create a `.env` file in your project root: ```bash theme={null} PAUBOX_API_KEY=YOUR_API_KEY ``` The SDK loads this value automatically. To obtain your API key, see [Authentication](/php-sdk/authentication). Create a file `send.php`: ```php theme={null} setSubject("Your first Paubox email"); $header->setFrom("sender@yourdomain.com"); $content = new Paubox\Mail\Content(); $content->setPlainText("This message was sent with the Paubox PHP SDK."); $message->setHeader($header); $message->setContent($content); $message->setRecipients(["recipient@example.com"]); $response = $paubox->sendMessage($message); echo "Sent. Tracking ID: " . $response->sourceTrackingId . PHP_EOL; ``` Run it: ```bash theme={null} php send.php ``` ``` Sent. Tracking ID: abc123-def456-... ``` Use the tracking ID to check delivery: ```php theme={null} $disposition = $paubox->getEmailDisposition($response->sourceTrackingId); foreach ($disposition->data->message->message_deliveries as $delivery) { echo $delivery->recipient . " → " . $delivery->status->deliveryStatus . PHP_EOL; } ``` ``` recipient@example.com → delivered ``` ## Next steps * [Email client](/php-sdk/email-client): add HTML, attachments, CC/BCC, and handle errors * [Forms client](/php-sdk/forms-client): retrieve form schemas and submit responses # Authentication Source: https://docs.paubox.com/python-sdk/authentication Configure your Paubox API credentials for the Python SDK. ## Obtain your credentials The Email API client requires two values: * **API key**: a secret token that authenticates your requests * **API host**: the API endpoint URL, `https://api.paubox.com/v1` Your API key is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Environment variables (recommended) Set these in your shell or a `.env` file loaded by your application: ```bash theme={null} PAUBOX_API_KEY=YOUR_API_KEY PAUBOX_HOST=https://api.paubox.com/v1 ``` The SDK reads these automatically; no constructor arguments needed: ```python theme={null} from paubox import PauboxApiClient client = PauboxApiClient() ``` ## Constructor parameters Pass credentials directly if you prefer not to use environment variables: ```python theme={null} client = PauboxApiClient( api_key = 'YOUR_API_KEY', host = 'https://api.paubox.com/v1' ) ``` The SDK sets the `Authorization` header automatically on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Forms client `PauboxFormsClient` requires no credentials: ```python theme={null} from paubox import PauboxFormsClient forms_client = PauboxFormsClient() ``` ## Security notes * Never hard-code credentials in source files. * The SDK does not log API keys or request bodies. # Email client Source: https://docs.paubox.com/python-sdk/email-client Full reference for the Paubox Python SDK email client: composing messages, sending email, tracking delivery, and error handling. ## Instantiation ```python theme={null} from paubox import PauboxApiClient # Read credentials from environment variables client = PauboxApiClient() # Or pass credentials directly client = PauboxApiClient( api_key = 'YOUR_API_KEY', host = 'https://api.paubox.com/v1' ) ``` See [Authentication](/python-sdk/authentication) for both approaches. ## Composing a message Use the `Mail` helper to compose messages. It handles formatting and automatically base64-encodes HTML content before sending. ```python theme={null} from paubox.helpers.mail import Mail mail = Mail( from_ = 'sender@yourdomain.com', # required subject = 'Your results are ready', # required recipients = ['alice@example.com'], # required; list of email strings content = { # required; at least one key 'text/plain': 'Plain text body.', 'text/html': '

HTML body.

' # auto base64-encoded }, optional_headers = { # optional 'reply_to': 'support@yourdomain.com', 'cc': ['manager@example.com'], 'bcc': 'audit@example.com', # str or list 'allowNonTLS': False, 'forceSecureNotification': False, 'attachments': [attachment] } ) ``` Call `mail.get()` to get the formatted dict ready for the API. ### Attachments ```python theme={null} import base64 with open('report.pdf', 'rb') as f: encoded = base64.b64encode(f.read()).decode('utf-8') attachment = { 'fileName': 'report.pdf', 'contentType': 'application/pdf', 'content': encoded } ``` Pass attachments via the `optional_headers` dict: ```python theme={null} optional_headers = { 'attachments': [attachment] } ``` ## Send a message ```python theme={null} response = client.send(mail.get()) print('Status:', response.status_code) print('Tracking ID:', response.to_dict.get('sourceTrackingId')) ``` ## Check delivery status ```python theme={null} tracking_id = response.to_dict['sourceTrackingId'] disposition = client.get(tracking_id) deliveries = disposition.to_dict['data']['message']['message_deliveries'] for d in deliveries: print(d['recipient'], '→', d['status']['deliveryStatus']) ``` Common `deliveryStatus` values: `delivered`, `opened`, `failed`, `pending`. ## The Response object Both `send` and `get` return a `Response` object: | Property | Description | | -------------- | ------------------------------------------------ | | `.status_code` | HTTP status code | | `.headers` | Response headers dict | | `.text` | Raw response body as a string | | `.to_dict` | JSON-parsed response body, or `None` if not JSON | ## Error handling Both methods raise `requests.exceptions.HTTPError` on non-2xx responses. Wrap calls in a try/except block: ```python theme={null} import requests try: response = client.send(mail.get()) except requests.exceptions.HTTPError as e: print('API error:', e.response.status_code, e.response.text) ``` The optional `handle_error` helper prints the error response body before re-raising: ```python theme={null} from paubox.helpers.errors import handle_error try: response = client.send(mail.get()) except requests.exceptions.HTTPError as e: handle_error(e) # prints body, then re-raises ``` # Forms client Source: https://docs.paubox.com/python-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Python SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Instantiation ```python theme={null} from paubox import PauboxFormsClient client = PauboxFormsClient() ``` ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```python theme={null} response = client.get_form('your-form-uuid') form = response.to_dict print('Title:', form['title']) print('HTML:', form['form_html']) ``` Key fields in the response: | Field | Description | | ------------------ | ----------------------------------------- | | `title` | Display name of the form | | `form_json` | Parsed field schema | | `form_html` | Rendered HTML for embedding | | `form_css` | Associated stylesheet | | `active` | Whether the form is accepting submissions | | `submission_count` | Number of submissions received | ## Submit a form ```python theme={null} form_data = { 'first_name': 'Jane', 'last_name': 'Smith', 'email': 'jane@example.com' } response = client.submit_form('your-form-uuid', form_data) print('Status:', response.status_code) # 201 on success ``` `submit_form` raises `ValueError` if `form_data` is empty or `None`. ### Submitting with file attachments The maximum total request size is 250 MB. ```python theme={null} import base64 with open('consent.pdf', 'rb') as f: encoded = base64.b64encode(f.read()).decode('utf-8') attachments = [ {'name': 'consent.pdf', 'content': encoded} ] response = client.submit_form('your-form-uuid', form_data, attachments) ``` Each attachment is a dict with two keys: | Key | Description | | --------- | ----------------------------- | | `name` | File name including extension | | `content` | Base64-encoded file content | ## Error handling ```python theme={null} import requests try: response = client.get_form('your-form-uuid') response = client.submit_form('your-form-uuid', form_data) except ValueError as e: print('Invalid input:', e) except requests.exceptions.HTTPError as e: print('API error:', e.response.status_code, e.response.text) ``` # Paubox Python SDK Source: https://docs.paubox.com/python-sdk/index Send HIPAA compliant email and submit Paubox Forms from Python 3 applications using the official Paubox Python SDK. The Paubox Python SDK is the official Python 3 client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). It provides a clean interface for sending encrypted email, tracking delivery, and submitting form responses with a single external dependency (`requests`). ```bash theme={null} pip3 install paubox-python3 ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Add CC, BCC, attachments, and HTML or plain-text content * Track delivery status and engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * Python 3 * `requests` (installed automatically) ## Get started Install, configure credentials, and send your first email in minutes. Set up your API key and endpoint host via environment variables. Full reference for composing and sending email, and checking delivery status. Retrieve form schemas and submit responses. ## Version note The current stable release (v1) covers sending email and the Forms API. A v2 beta on the `sdk-generation/v2.0.0-beta` branch adds bulk sending and dynamic templates. This documentation covers v1. # Quickstart Source: https://docs.paubox.com/python-sdk/quickstart Install the Paubox Python SDK and send your first HIPAA compliant email in minutes. ```bash theme={null} pip3 install paubox-python3 ``` Export your API key and endpoint host as environment variables: ```bash theme={null} export PAUBOX_API_KEY="YOUR_API_KEY" export PAUBOX_HOST="https://api.paubox.com/v1" ``` To obtain your API key, see [Authentication](/python-sdk/authentication). Create `send.py`: ```python theme={null} from paubox import PauboxApiClient from paubox.helpers.mail import Mail client = PauboxApiClient() mail = Mail( from_ = 'sender@yourdomain.com', subject = 'Your first Paubox email', recipients = ['recipient@example.com'], content = {'text/plain': 'This message was sent with the Paubox Python SDK.'} ) response = client.send(mail.get()) print('Status:', response.status_code) print('Response:', response.to_dict) ``` Run it: ```bash theme={null} python3 send.py ``` The response dict contains `sourceTrackingId`. ```python theme={null} tracking_id = response.to_dict['sourceTrackingId'] disposition = client.get(tracking_id) print(disposition.to_dict) ``` The response includes `message_deliveries` with per-recipient status. ## Next steps * [Email client](/python-sdk/email-client): add HTML, attachments, CC/BCC, and handle errors * [Forms client](/python-sdk/forms-client): retrieve form schemas and submit responses # Authentication Source: https://docs.paubox.com/rails-sdk/authentication Configure your Paubox API credentials in a Rails initializer. ## Obtain your API key The Email API requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Configure the initializer Create `config/initializers/paubox.rb`: ```ruby theme={null} Paubox.configure do |config| config.api_key = ENV['PAUBOX_API_KEY'] end ``` The gem registers itself as a Railtie, so this initializer runs before ActionMailer is configured. Credentials are automatically applied to every email delivery; no per-request setup is needed. ## Set environment variables Add your API key to your environment (e.g. via a `.env` file with `dotenv-rails`, Rails credentials, or your deployment platform's secret management): ```bash theme={null} PAUBOX_API_KEY=YOUR_API_KEY ``` ## Forms client `PauboxRails::Forms::Client` requires no credentials. Form submissions are associated with the form ID, not an API key. ```ruby theme={null} client = PauboxRails::Forms.client ``` ## Security notes * Never hard-code credentials in initializer files; always read from environment variables or encrypted credentials. * The gem does not log API keys or request bodies. # Email delivery Source: https://docs.paubox.com/rails-sdk/email-delivery Send HIPAA compliant email through ActionMailer using the Paubox Rails gem. ## How it works `paubox_rails` registers `:paubox` as an ActionMailer delivery method. Your mailer classes are standard Rails mailers; the gem intercepts delivery and routes each message through the Paubox Email API, ensuring every email is encrypted and HIPAA compliant. ## Set the delivery method **Globally** (all environments): ```ruby theme={null} # config/application.rb config.action_mailer.delivery_method = :paubox ``` **Per environment** (recommended for production only): ```ruby theme={null} # config/environments/production.rb config.action_mailer.delivery_method = :paubox ``` Leave development and test environments using `:letter_opener`, `:test`, or another local delivery method. ## Create a mailer Mailers work exactly as documented in the [Rails ActionMailer guide](https://guides.rubyonrails.org/action_mailer_basics.html): ```ruby theme={null} class PatientMailer < ApplicationMailer def appointment_reminder @patient = params[:patient] @appointment = params[:appointment] mail( to: @patient.email, from: "appointments@yourclinic.com", subject: "Appointment reminder" ) end end ``` Templates live in `app/views/patient_mailer/` as usual (`appointment_reminder.html.erb`, `appointment_reminder.text.erb`). ## Deliver email ```ruby theme={null} # Synchronous delivery PatientMailer.with(patient: patient, appointment: appt).appointment_reminder.deliver_now # Asynchronous delivery (via Active Job) PatientMailer.with(patient: patient, appointment: appt).appointment_reminder.deliver_later ``` ## Per-message options Pass `delivery_method_options` in the `mail()` call to override delivery behavior for a specific message: ```ruby theme={null} mail( to: @patient.email, subject: "Lab results", delivery_method_options: { allow_non_tls: true } ) ``` ## Attachments Attach files using standard ActionMailer attachment helpers: ```ruby theme={null} def report_email attachments["results.pdf"] = File.read(Rails.root.join("tmp/results.pdf")) mail( to: params[:patient].email, subject: "Your lab results" ) end ``` ## Tracking IDs The ActionMailer delivery flow does not surface per-send tracking IDs. If you need to record and look up a tracking ID for a specific send, use the [Paubox Email API](/email-api) directly via a REST client alongside this gem. # Forms client Source: https://docs.paubox.com/rails-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Rails SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Create a client ```ruby theme={null} client = PauboxRails::Forms.client ``` `PauboxRails::Forms.client` returns a new `PauboxRails::Forms::Client` instance. ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```ruby theme={null} form = client.get_form("your-form-uuid") puts form["title"] puts form["form_html"] puts form["form_json"].inspect ``` The return value is a Hash with the following keys: | Key | Description | | -------------------- | ----------------------------------------- | | `"title"` | Display name of the form | | `"form_json"` | Parsed field schema | | `"form_html"` | Rendered HTML for embedding | | `"form_css"` | Associated stylesheet | | `"active"` | Whether the form is accepting submissions | | `"submission_count"` | Number of submissions received | `get_form` raises `PauboxRails::Forms::NotFoundError` if the form UUID is invalid. ## Submit a form ```ruby theme={null} result = client.submit_form( "your-form-uuid", form_data: { "first_name" => "Jane", "last_name" => "Smith", "email" => "jane@example.com" } ) # result => true ``` `submit_form` returns `true` on a successful submission (HTTP 201). ### Submitting with file attachments The maximum total request size is 250 MB. ```ruby theme={null} require "base64" encoded = Base64.strict_encode64(File.read(Rails.root.join("tmp/consent.pdf"))) result = client.submit_form( "your-form-uuid", form_data: { "first_name" => "Jane" }, attachments: [ { name: "consent.pdf", content: encoded } ] ) ``` Each attachment is a Hash with two keys: | Key | Description | | ---------- | ----------------------------- | | `:name` | File name including extension | | `:content` | Base64-encoded file content | ## Error handling ```ruby theme={null} begin form = client.get_form("your-form-uuid") result = client.submit_form("your-form-uuid", form_data: data) rescue PauboxRails::Forms::NotFoundError # Form UUID not found (HTTP 404) rescue PauboxRails::Forms::BadRequestError # Malformed request (HTTP 400) rescue PauboxRails::Forms::Error => e # Any other Forms API error Rails.logger.error("Forms error: #{e.message}") end ``` # Paubox Rails SDK Source: https://docs.paubox.com/rails-sdk/index Send HIPAA compliant email from Rails applications using ActionMailer and the official Paubox Rails gem. The Paubox Rails SDK (`paubox_rails`) integrates the [Paubox Email API](/email-api) directly into Rails as an ActionMailer delivery method. Your mailer classes work exactly as they do today; just point ActionMailer at Paubox and every email you send becomes encrypted and HIPAA compliant. The gem also includes a lightweight client for the [Paubox Forms API](/forms). ```ruby theme={null} # Gemfile gem 'paubox_rails' ``` ## What you can do * Send encrypted, HIPAA compliant email through standard Rails mailers and ActionMailer * Use HTML and plain-text templates, attachments, and all standard ActionMailer features * Deliver synchronously with `deliver_now` or asynchronously with `deliver_later` * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * Rails 4 or later * Ruby * Dependencies installed automatically: `actionmailer` and the `paubox` gem ## Get started Add the gem, configure credentials, and send your first email in minutes. Configure your API key via a Rails initializer. Full guide to sending email through ActionMailer with Paubox. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/rails-sdk/quickstart Add the Paubox Rails gem, configure credentials, and send your first HIPAA compliant email in minutes. Add `paubox_rails` to your Gemfile: ```ruby theme={null} gem 'paubox_rails' ``` Then install: ```bash theme={null} bundle install ``` Create `config/initializers/paubox.rb`: ```ruby theme={null} Paubox.configure do |config| config.api_key = ENV['PAUBOX_API_KEY'] end ``` To obtain credentials, see [Authentication](/rails-sdk/authentication). In `config/application.rb` (or an environment-specific file): ```ruby theme={null} config.action_mailer.delivery_method = :paubox ``` Generate a mailer if you don't have one: ```bash theme={null} rails generate mailer UserMailer ``` Define an action in `app/mailers/user_mailer.rb`: ```ruby theme={null} class UserMailer < ApplicationMailer def welcome_email @user = params[:user] mail( to: @user.email, from: "noreply@yourdomain.com", subject: "Welcome to the portal" ) end end ``` Add a template at `app/views/user_mailer/welcome_email.html.erb`: ```erb theme={null}

Hello <%= @user.name %>,

Your account is ready. Log in at any time.

``` Deliver it: ```ruby theme={null} UserMailer.with(user: current_user).welcome_email.deliver_now ``` The email is sent encrypted via Paubox.
## Next steps * [Email delivery](/rails-sdk/email-delivery): per-message options, attachments, async delivery * [Forms client](/rails-sdk/forms-client): retrieve form schemas and submit responses # Authentication Source: https://docs.paubox.com/ruby-sdk/authentication Configure your Paubox API credentials for the Ruby SDK. ## Obtain your credentials The Email API client requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Global configuration (recommended) Set your API key once at application startup. All `Paubox::Client` instances will use it automatically: ```ruby theme={null} require 'paubox' Paubox.configure do |config| config.api_key = ENV['PAUBOX_API_KEY'] end client = Paubox::Client.new ``` ## Per-client configuration Override the API key for a specific client instance: ```ruby theme={null} client = Paubox::Client.new(api_key: 'YOUR_API_KEY') ``` The SDK sets the `Authorization` header automatically on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Forms client `Paubox::FormsClient` requires no credentials: ```ruby theme={null} forms_client = Paubox::FormsClient.new ``` ## Security notes * Never hard-code credentials in source files; always read from environment variables. * The SDK does not log API keys or request bodies. # Email client Source: https://docs.paubox.com/ruby-sdk/email-client Full reference for the Paubox Ruby SDK email client: building messages, sending email, tracking delivery, dynamic templates, and error handling. ## The client ```ruby theme={null} client = Paubox::Client.new # uses global Paubox.configure credentials client = Paubox::Client.new(api_key: 'YOUR_API_KEY') # or a per-client API key ``` `send_mail` and `deliver_mail` are aliases. `email_disposition` and `message_receipt` are aliases. ## Building a message ```ruby theme={null} message = Paubox::Message.new( from: 'sender@yourdomain.com', # required to: ['alice@example.com'], # required; array subject: 'Your results are ready', # optional reply_to: 'support@yourdomain.com', # optional cc: ['manager@example.com'], # optional bcc: ['audit@example.com'], # optional text_content: 'Plain text body.', # at least one required html_content: '

HTML body.

', # optional allow_non_tls: false, # optional; default false force_secure_notification: false # optional; default false ) ``` ### Attachments Attach files by path using the helper method: ```ruby theme={null} message.add_attachment('/path/to/report.pdf') ``` Or set the `attachments` attribute directly with an array of hashes (each with `fileName`, `contentType`, and base64-encoded `content`): ```ruby theme={null} require 'base64' message.attachments = [ { fileName: 'report.pdf', contentType: 'application/pdf', content: Base64.strict_encode64(File.read('/path/to/report.pdf')) } ] ``` ## Send a message ```ruby theme={null} response = client.send_mail(message) puts response[:source_tracking_id] ``` ## Check delivery status ```ruby theme={null} disposition = client.email_disposition(source_tracking_id) disposition.message_deliveries.each do |delivery| puts "#{delivery.recipient} → #{delivery.status.delivery_status}" puts " Opened: #{delivery.status.opened_status}" end ``` `EmailDisposition` fields: | Field | Description | | -------------------- | ---------------------------------- | | `source_tracking_id` | The tracking ID | | `message_deliveries` | Array of `MessageDelivery` structs | | `errors` | Array of `ResponseError` structs | Each `MessageDelivery` struct has `recipient` and `status`. The `status` struct has `delivery_status`, `delivery_time`, `opened_status`, and `opened_time`. Check `disposition.errors?` to detect API-level errors. Common `delivery_status` values: `delivered`, `opened`, `failed`, `pending`. ## Dynamic templates Templates use [Handlebars](https://handlebarsjs.com) syntax (`{{variable_name}}`). ### Create a template ```ruby theme={null} Paubox::DynamicTemplates.create('appointment-confirmation', '/path/to/template.html') ``` ### List templates ```ruby theme={null} templates = Paubox::DynamicTemplates.list templates.each { |t| puts "#{t[:id]}: #{t[:name]}" } ``` ### Get a template ```ruby theme={null} template = Paubox::DynamicTemplates.find(template_id) ``` ### Update a template ```ruby theme={null} template.update('/path/to/updated-template.html') # or rename: template.update('/path/to/updated-template.html', 'new-template-name') ``` ### Delete a template ```ruby theme={null} template.delete ``` ### Send a templated message ```ruby theme={null} message = Paubox::TemplatedMessage.new( from: 'appointments@yourclinic.com', to: ['jane@example.com'], subject: 'Your appointment is confirmed', template: { name: 'appointment-confirmation', values: { first_name: 'Jane', date: '2024-03-15', time: '2:00 PM' } } ) response = client.send_mail(message) ``` ## Error handling Methods raise `RestClient::ExceptionWithResponse` on HTTP errors. Rescue it to inspect the response: ```ruby theme={null} require 'rest-client' begin response = client.send_mail(message) rescue RestClient::ExceptionWithResponse => e puts "HTTP #{e.response.code}: #{e.response.body}" end ``` # Forms client Source: https://docs.paubox.com/ruby-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Ruby SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Instantiation ```ruby theme={null} require 'paubox' forms_client = Paubox::FormsClient.new ``` ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```ruby theme={null} form = forms_client.get_form('your-form-uuid') puts form.title puts form.form_html ``` The returned `Paubox::Form` object exposes: | Method / Attribute | Description | | -------------------------- | ----------------------------------------- | | `#title` | Display name of the form | | `#form_json` | Parsed field schema | | `#form_html` | Rendered HTML for embedding | | `#form_css` | Associated stylesheet | | `#active?` | Whether the form is accepting submissions | | `#submission_count` | Number of submissions received | | `#signable?` | Whether the form supports e-signatures | | `#deleted?` / `#archived?` | Form state flags | ## Submit a form ```ruby theme={null} form_data = { first_name: 'Jane', last_name: 'Smith', email: 'jane@example.com' } response = forms_client.submit_form('your-form-uuid', form_data: form_data) # response is 201 Created on success ``` ### Submitting with file attachments The maximum total request size is 250 MB. ```ruby theme={null} require 'base64' encoded = Base64.strict_encode64(File.read('consent.pdf')) attachments = [ { name: 'consent.pdf', content: encoded } ] response = forms_client.submit_form( 'your-form-uuid', form_data: form_data, attachments: attachments ) ``` ## Error handling ```ruby theme={null} require 'rest-client' begin form = forms_client.get_form('your-form-uuid') response = forms_client.submit_form('your-form-uuid', form_data: form_data) rescue RestClient::ExceptionWithResponse => e puts "HTTP #{e.response.code}: #{e.response.body}" end ``` # Paubox Ruby SDK Source: https://docs.paubox.com/ruby-sdk/index Send HIPAA compliant email and submit Paubox Forms from Ruby applications using the official Paubox Ruby gem. The Paubox Ruby SDK (`paubox` gem) is the core Ruby client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). It provides direct API access for sending encrypted email, managing dynamic templates, tracking delivery with typed response objects, and submitting form responses. ```ruby theme={null} # Gemfile gem 'paubox' ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Create, update, and delete dynamic Handlebars templates * Send templated messages with per-message variable substitution * Track delivery status and open engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * Ruby >= 2.3 * Dependencies installed automatically: `mail` and `rest-client` ## Rails applications If you are using Rails, the [`paubox_rails`](/rails-sdk) gem builds on top of this library and adds ActionMailer integration. Use `paubox` directly for non-Rails Ruby applications, scripts, or background jobs. ## Get started Configure credentials and send your first email in minutes. Configure globally or per-client with your API key. Full reference for sending email, managing templates, and tracking delivery. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/ruby-sdk/quickstart Install the Paubox Ruby gem and send your first HIPAA compliant email in minutes. Add to your Gemfile: ```ruby theme={null} gem 'paubox' ``` Then install: ```bash theme={null} bundle install ``` Or install directly: ```bash theme={null} gem install paubox ``` ```ruby theme={null} require 'paubox' Paubox.configure do |config| config.api_key = ENV['PAUBOX_API_KEY'] end ``` To obtain your API key, see [Authentication](/ruby-sdk/authentication). ```ruby theme={null} client = Paubox::Client.new message = Paubox::Message.new( from: 'sender@yourdomain.com', to: ['recipient@example.com'], subject: 'Your first Paubox email', text_content: 'This message was sent with the Paubox Ruby SDK.' ) response = client.send_mail(message) puts "Sent. Tracking ID: #{response[:source_tracking_id]}" ``` ```ruby theme={null} tracking_id = response[:source_tracking_id] disposition = client.email_disposition(tracking_id) disposition.message_deliveries.each do |delivery| puts "#{delivery.recipient} → #{delivery.status.delivery_status}" end ``` ## Next steps * [Email client](/ruby-sdk/email-client): attachments, dynamic templates, error handling * [Forms client](/ruby-sdk/forms-client): retrieve form schemas and submit responses # Authentication Source: https://docs.paubox.com/rust-sdk/authentication Configure your Paubox API credentials for the Rust SDK. ## Obtain your credentials The Email API client requires one credential: * **API key**: a secret token that authenticates your requests It is available in the [Paubox dashboard](https://next.paubox.com) under **API Credentials**. ## Option 1: Environment variables (recommended) ```bash theme={null} export PAUBOX_API_KEY="YOUR_API_KEY" ``` ```rust theme={null} let client = PauboxClient::from_env()?; ``` `from_env()` returns `Err(PauboxError::EnvVar(...))` immediately if the variable is missing or empty. ## Option 2: Direct constructor ```rust theme={null} let client = PauboxClient::new("YOUR_API_KEY"); ``` ## Option 3: Builder (with custom timeout or base URL) ```rust theme={null} use std::time::Duration; let client = PauboxClient::builder() .api_key("YOUR_API_KEY") .timeout(Duration::from_secs(15)) .build()?; ``` Use `.base_url(url)` in tests to point at a mock server. The SDK sets the `Authorization` header automatically on every request: ``` Authorization: Token token=YOUR_API_KEY ``` ## Forms client `FormsClient` requires no credentials: ```rust theme={null} let forms = FormsClient::new(); ``` If you already have a `PauboxClient`, reuse its HTTP connection pool: ```rust theme={null} let forms = email_client.forms(); ``` ## Security notes * Never hard-code credentials in source files. * The SDK does not log API keys or request bodies. # Email client Source: https://docs.paubox.com/rust-sdk/email-client Full reference for the Paubox Rust SDK email client: building messages, sending email, tracking delivery, and error handling. ## Creating a client ```rust theme={null} // From environment variables (recommended) let client = PauboxClient::from_env()?; // Direct constructor let client = PauboxClient::new("YOUR_API_KEY"); // Builder (custom timeout, base URL for testing) let client = PauboxClient::builder() .api_key("YOUR_API_KEY") .timeout(Duration::from_secs(15)) .build()?; ``` See [Authentication](/rust-sdk/authentication) for details on all three options. ## Building a message Use `Message::builder()` to compose a message. All methods take `impl Into` or iterables: ```rust theme={null} use paubox::Message; let message = Message::builder() .from("sender@yourdomain.com") // required .to(["alice@example.com"]) // required; accepts any iterable .subject("Your results are ready") // required .text_content("Plain text body.") // at least one of text/html required .html_content("

HTML body.

") // optional .reply_to("support@yourdomain.com") // optional .cc(["manager@example.com"]) // optional .bcc(["audit@example.com"]) // optional .allow_non_tls(false) // optional; default false .force_secure_notification(true) // optional .build()?; ``` `.build()` validates required fields and returns `Err(PauboxError::Validation(...))` if any are missing. ### Attachments The `from_bytes` constructor base64-encodes the data automatically; no manual encoding needed: ```rust theme={null} use paubox::Attachment; use std::fs; let data = fs::read("report.pdf")?; let attachment = Attachment::from_bytes("report.pdf", "application/pdf", &data); ``` If you already have base64-encoded content: ```rust theme={null} let attachment = Attachment::from_base64("report.pdf", "application/pdf", encoded_string); ``` Add attachments to a message via the builder: ```rust theme={null} let message = Message::builder() // ...other fields... .attachment(attachment) .build()?; ``` ## Send a message ```rust theme={null} let response = client.send_message(&message).await?; println!("Tracking ID: {}", response.source_tracking_id); ``` `SendResponse` fields: | Field | Description | | -------------------- | ---------------------------- | | `source_tracking_id` | Use to check delivery status | | `message` | Status message from the API | ## Check delivery status ```rust theme={null} let disposition = client .get_email_disposition(&response.source_tracking_id) .await?; for delivery in &disposition.message_deliveries { println!( "{} → {} (opened: {})", delivery.recipient, delivery.delivery_status, delivery.opened_status ); } ``` `MessageDelivery` fields: | Field | Description | | ----------------- | ------------------------------------- | | `recipient` | Email address | | `delivery_status` | e.g. `delivered`, `failed`, `pending` | | `delivery_time` | ISO 8601 timestamp, if delivered | | `opened_status` | `opened` or `unopened` | | `opened_time` | ISO 8601 timestamp, if opened | ## Health check ```rust theme={null} client.api_status().await?; ``` Returns `Ok(())` if the API is reachable, or a `PauboxError` if not. ## Error handling All methods return `Result`. Match on the variants to handle specific conditions: ```rust theme={null} use paubox::PauboxError; match client.send_message(&message).await { Ok(response) => println!("Sent: {}", response.source_tracking_id), Err(PauboxError::Auth(msg)) => eprintln!("Invalid credentials: {}", msg), Err(PauboxError::Http { status, body }) => { eprintln!("HTTP {}: {}", status, body); } Err(PauboxError::Validation(msg)) => eprintln!("Invalid message: {}", msg), Err(e) => eprintln!("Error: {}", e), } ``` `PauboxError` variants: | Variant | When it occurs | | -------------------------------- | -------------------------------------------- | | `Auth(String)` | HTTP 401: invalid or missing credentials | | `Http { status, body }` | Non-2xx response other than 401 | | `Request(reqwest::Error)` | Network or TLS failure | | `Deserialize(serde_json::Error)` | Unexpected response format | | `Validation(String)` | Missing required message field | | `EnvVar(String)` | Missing environment variable in `from_env()` | | `Url(url::ParseError)` | URL construction failure | # Forms client Source: https://docs.paubox.com/rust-sdk/forms-client Retrieve Paubox Form schemas and submit form responses using the Rust SDK. The Forms client does not require API credentials. Forms are identified by a UUID that you obtain from the Paubox dashboard. ## Creating a client **Standalone** (creates its own HTTP connection pool): ```rust theme={null} use paubox::FormsClient; let client = FormsClient::new(); ``` **Shared connection pool** (reuses the HTTP client from an existing `PauboxClient`): ```rust theme={null} let forms = email_client.forms(); ``` ## Get a form Retrieve a form's metadata, field schema, and rendered HTML/CSS: ```rust theme={null} let form = client.get_form("your-form-uuid").await?; println!("Title: {}", form.title); if let Some(html) = &form.form_html { println!("HTML: {}", html); } ``` `Form` fields: | Field | Type | Description | | ------------------ | --------------------------- | ----------------------------------------- | | `title` | `String` | Display name of the form | | `form_json` | `Option` | Parsed field schema | | `form_html` | `Option` | Rendered HTML for embedding | | `form_css` | `Option` | Associated stylesheet | | `active` | `bool` | Whether the form is accepting submissions | | `submission_count` | `u64` | Number of submissions received | ## Submit a form Build a `FormSubmission` and call `submit_form`: ```rust theme={null} use paubox::FormSubmission; use serde_json::json; let submission = FormSubmission::builder() .form_data(json!({ "first_name": "Jane", "last_name": "Smith", "email": "jane@example.com" })) .build()?; client.submit_form("your-form-uuid", &submission).await?; ``` `submit_form` returns `Ok(())` on success (HTTP 201). ### Submitting with file attachments The maximum total request size is 250 MB. `FormAttachment::from_bytes` base64-encodes automatically: ```rust theme={null} use paubox::{FormAttachment, FormSubmission}; use std::fs; let data = fs::read("consent.pdf")?; let attachment = FormAttachment::from_bytes("consent.pdf", &data); let submission = FormSubmission::builder() .form_data(json!({ "first_name": "Jane" })) .attachment(attachment) .build()?; client.submit_form("your-form-uuid", &submission).await?; ``` If you already have base64-encoded content: ```rust theme={null} let attachment = FormAttachment::from_base64("consent.pdf", encoded_string); ``` ## Error handling ```rust theme={null} use paubox::PauboxError; match client.get_form("your-form-uuid").await { Ok(form) => println!("Form: {}", form.title), Err(PauboxError::Http { status, body }) => { eprintln!("HTTP {}: {}", status, body); } Err(e) => eprintln!("Error: {}", e), } ``` # Paubox Rust SDK Source: https://docs.paubox.com/rust-sdk/index Send HIPAA compliant email and submit Paubox Forms from Rust applications using the official Paubox Rust SDK. The Paubox Rust SDK is the official async Rust client library for the [Paubox Email API](/email-api) and [Paubox Forms](/forms). All methods are `async` and return `Result`, making them composable with any Tokio-based application. ```toml theme={null} # Cargo.toml [dependencies] paubox = "0.1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } ``` ## What you can do * Send encrypted, HIPAA compliant email to one or more recipients * Add CC, BCC, attachments, and HTML or plain-text content * Track delivery status and open engagement per recipient * Retrieve Paubox Form schemas and submit form responses with file attachments ## Requirements * Rust 1.75 or later * Tokio async runtime ## Feature flags Both features are enabled by default. Disable the ones you don't need to reduce compile times: ```toml theme={null} # Email API only paubox = { version = "0.1", default-features = false, features = ["email"] } # Forms API only paubox = { version = "0.1", default-features = false, features = ["forms"] } ``` | Feature | What it includes | | ------- | ------------------------------------------------------------------------------ | | `email` | `PauboxClient`, `Message`, `Attachment`, `SendResponse`, `DispositionResponse` | | `forms` | `FormsClient`, `Form`, `FormSubmission`, `FormAttachment` | ## Get started Configure credentials and send your first email in minutes. Three ways to supply your API key. Full reference for building messages, sending email, and tracking delivery. Retrieve form schemas and submit responses. # Quickstart Source: https://docs.paubox.com/rust-sdk/quickstart Install the Paubox Rust SDK and send your first HIPAA compliant email in minutes. Add `paubox` and `tokio` to your `Cargo.toml`: ```toml theme={null} [dependencies] paubox = "0.1" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } ``` Export your API key as an environment variable: ```bash theme={null} export PAUBOX_API_KEY="YOUR_API_KEY" ``` To obtain your API key, see [Authentication](/rust-sdk/authentication). Create `src/main.rs`: ```rust theme={null} use paubox::{Message, PauboxClient}; #[tokio::main] async fn main() -> Result<(), Box> { let client = PauboxClient::from_env()?; let message = Message::builder() .from("sender@yourdomain.com") .to(["recipient@example.com"]) .subject("Your first Paubox email") .text_content("This message was sent with the Paubox Rust SDK.") .build()?; let response = client.send_message(&message).await?; println!("Sent. Tracking ID: {}", response.source_tracking_id); Ok(()) } ``` Run it: ```bash theme={null} cargo run ``` ``` Sent. Tracking ID: abc123-def456-... ``` ```rust theme={null} let disposition = client .get_email_disposition(&response.source_tracking_id) .await?; for delivery in &disposition.message_deliveries { println!("{} → {}", delivery.recipient, delivery.delivery_status); } ``` ``` recipient@example.com → delivered ``` ## Next steps * [Email client](/rust-sdk/email-client): attachments, CC/BCC, error handling * [Forms client](/rust-sdk/forms-client): retrieve form schemas and submit responses # SDKs Source: https://docs.paubox.com/sdks Official client libraries for the Paubox Email API and Forms API. Paubox provides official SDKs for nine languages. Each library handles authentication, request formatting, and error handling so you can integrate in minutes. Source code and issue trackers for all SDKs are available on [GitHub](https://github.com/paubox). # Introduction Source: https://docs.paubox.com/welcome Paubox is HIPAA compliant, HITRUST certified, email infrastructure. Send transactional email, run marketing campaigns, and collect patient data via forms, all without worrying about PHI exposure. ## Which product is right for me? | I want to… | Use… | | :---------------------------------------------- | :----------------------------------- | | Send transactional email from my app | [Email API](/email-api/quickstart) | | Run email campaigns to patient lists | [Marketing API](/marketing) | | Collect patient intake or consent data | [Forms API](/forms/index) | | Send from the terminal or CI pipeline | [CLI](/cli/quickstart) | | Use Paubox from Claude or AI agents | [MCP Server](/mcp-server/quickstart) | | Use Paubox from my app (Go, Node, Python, etc.) | [SDKs](/sdks) | ## Products Send transactional, HIPAA compliant email via REST or SMTP Campaigns, drip sequences, subscribers, and analytics Collect patient data via HIPAA compliant secure forms Send email and check delivery from the terminal or CI Give AI assistants access to Paubox Email and Forms tools ## Get started Four steps to your first email Auth, formatting, and response codes Send email via HTTP Send email via SMTP ## Quickstart by language ## Building an SDK or integration? Start with our OpenAPI specs: point your generator, AI agent, or editor at them directly. Transactional email: `api.paubox.com/v1/email` Campaigns, subscribers, analytics: `api.paubox.com/v1/marketing` ## Need help? SDK source and issues System uptime Contact the team Have a question, an idea, or something you built to show off? Join the [Paubox Community](https://github.com/Paubox/community/discussions), the single home for discussions across every Paubox SDK and API (Email API, Forms, Marketing, and the MCP/CLI tooling). Never post PHI, recipient addresses, or message content in public threads. Anything sensitive goes to [support@paubox.com](mailto:support@paubox.com).