SDKs and HTTP Clients
SDKs and HTTP Clients
Postscale is a JSON REST API with official Node.js/TypeScript, Python, Ruby, and
PHP SDKs for server-side applications. Use the SDK for Node 20+, Python 3.10+,
Ruby 3.0+, or PHP 8.1+ projects, or call the REST API directly with curl,
fetch, your language's standard HTTP client, or any mature HTTP library.
Node.js and TypeScript SDK
Install the official npm package:
npm install @postscale/postscale
The package supports Node.js 20+, ESM and CommonJS, and ships TypeScript
declarations. It keeps Postscale's API-native snake_case fields so SDK calls,
REST examples, and the OpenAPI schema stay aligned.
import { Postscale } from "@postscale/postscale";
const postscale = new Postscale(process.env.POSTSCALE_API_KEY);
const { data, error } = await postscale.emails.send({
from: "hello@yourapp.com",
to: ["user@example.com"],
subject: "Welcome!",
html_body: "<h1>Hello World</h1>",
});
if (error) {
throw new Error(error.message);
}
console.log("Email sent:", data?.message_id);
The package is published at
@postscale/postscale.
Use it only from trusted server-side code. Do not expose API keys in browser
bundles.
Configuration
By default, the SDK reads POSTSCALE_API_KEY from the environment and sends
requests to https://api.postscale.io.
import { Postscale } from "@postscale/postscale";
const postscale = new Postscale(process.env.POSTSCALE_API_KEY, {
timeoutMs: 30_000,
});
Use a custom baseUrl only for local development, staging, or tests.
Error Handling
SDK resource methods return a Resend-style result object:
const { data, error, headers } = await postscale.emails.send({
from: "hello@yourapp.com",
to: ["user@example.com"],
subject: "Welcome!",
html_body: "<h1>Hello World</h1>",
});
if (error) {
console.error(error.code, error.message, headers.requestId);
return;
}
console.log(data?.status);
The SDK does not automatically retry non-idempotent email sends. This avoids duplicating messages unless the API request has an explicit idempotency contract.
Attachments
Use attachmentFromFile or pass base64 attachment objects directly:
import { Postscale, attachmentFromFile } from "@postscale/postscale";
const postscale = new Postscale();
const { error } = await postscale.emails.send({
from: "billing@yourapp.com",
to: ["customer@example.com"],
subject: "Invoice #1234",
html_body: "<p>Your invoice is attached.</p>",
attachments: [
await attachmentFromFile("./invoice-1234.pdf", "application/pdf"),
],
});
if (error) {
throw new Error(error.message);
}
Webhook Verification
The SDK includes a verifier for X-Postscale-Signature headers. Always pass
the exact raw request body bytes; do not re-serialize parsed JSON.
import { verifyWebhookSignature } from "@postscale/postscale";
const result = verifyWebhookSignature(
rawBody,
request.headers.get("x-postscale-signature"),
process.env.POSTSCALE_WEBHOOK_SECRET,
);
if (!result.valid) {
return new Response(result.message, { status: 401 });
}
The verifier supports the t=...,v1=... signature format and multiple
candidate secrets during rotation windows. See the
Webhooks guide for framework notes and raw
HMAC examples.
Python SDK
Install the official PyPI package:
pip install postscale
The package supports Python 3.10+ and uses httpx under the hood. It keeps
Postscale's API-native snake_case fields so SDK calls, REST examples, and the
OpenAPI schema stay aligned.
import os
from postscale import Postscale
postscale = Postscale(os.environ["POSTSCALE_API_KEY"])
result = postscale.emails.send({
"from": "hello@yourapp.com",
"to": ["user@example.com"],
"subject": "Welcome!",
"html_body": "<h1>Hello World</h1>",
})
if result.error:
raise RuntimeError(result.error.message)
print("Email sent:", result.data["message_id"])
The package is published at
postscale. Use it only from trusted
server-side code. Do not expose API keys in browser bundles.
Python Configuration
By default, the SDK reads POSTSCALE_API_KEY and POSTSCALE_BASE_URL from the
environment. You can pass an API key, timeout, retry count, custom headers, or an
httpx client explicitly when needed.
from postscale import Postscale
postscale = Postscale(
api_key="ps_live_your_api_key",
timeout=30,
max_retries=3,
)
Python Error Handling
Python resource methods return the same result shape as the Node SDK:
result = postscale.emails.send({
"from": "hello@yourapp.com",
"to": ["user@example.com"],
"subject": "Welcome!",
"html_body": "<h1>Hello World</h1>",
})
if result.error:
print(result.error.code, result.error.message, result.headers.request_id)
raise SystemExit(1)
print(result.data["status"])
The SDK retries retryable idempotent requests, honors Retry-After and
retry_after_seconds, and does not automatically retry non-idempotent email
sends.
Python Attachments
Use attachment_from_file or pass base64 attachment objects directly:
from postscale import Postscale, attachment_from_file
postscale = Postscale()
result = postscale.emails.send({
"from": "billing@yourapp.com",
"to": ["customer@example.com"],
"subject": "Invoice #1234",
"html_body": "<p>Your invoice is attached.</p>",
"attachments": [
attachment_from_file("./invoice-1234.pdf", "application/pdf"),
],
})
if result.error:
raise RuntimeError(result.error.message)
Attachment helpers enforce the documented client-side limits before sending: 10 attachments, 25 MB per attachment, and 50 MB total decoded attachment payload.
Python Webhook Verification
The Python SDK includes the same verifier for X-Postscale-Signature headers:
from postscale import verify_webhook_signature
result = verify_webhook_signature(
raw_body,
request.headers.get("X-Postscale-Signature"),
[current_secret, previous_secret],
)
if not result.valid:
return "invalid", 401
Ruby SDK
Install the official RubyGems package:
gem install postscale
The package supports Ruby 3.0+ and uses the Ruby standard library HTTP stack. It
keeps Postscale's API-native snake_case fields so SDK calls, REST examples,
and the OpenAPI schema stay aligned.
require "postscale"
postscale = Postscale::Client.new(api_key: ENV.fetch("POSTSCALE_API_KEY"))
result = postscale.emails.send(
from: "hello@yourapp.com",
to: ["user@example.com"],
subject: "Welcome!",
html_body: "<h1>Hello World</h1>"
)
if result.error
raise result.error.message
end
puts "Email sent: #{result.data['message_id']}"
The package is published at
postscale, and the source is available
at postscale/postscale-ruby.
Use it only from trusted server-side code. Do not expose API keys in browser
bundles.
Ruby Configuration
By default, the SDK reads POSTSCALE_API_KEY and POSTSCALE_BASE_URL from the
environment. You can pass an API key, timeout, retry count, or custom headers
explicitly when needed.
postscale = Postscale::Client.new(
api_key: "ps_live_your_api_key",
timeout: 30,
max_retries: 3,
headers: {}
)
Ruby Error Handling
Ruby resource methods return the same result shape as the Node and Python SDKs:
result = postscale.emails.send(
from: "hello@yourapp.com",
to: ["user@example.com"],
subject: "Welcome!",
html_body: "<h1>Hello World</h1>"
)
if result.error
warn "#{result.error.code}: #{result.error.message}"
warn result.headers.request_id
exit 1
end
puts result.data["status"]
The SDK retries retryable idempotent requests, honors Retry-After and
retry_after_seconds, and does not automatically retry non-idempotent email
sends.
Ruby Attachments
Use Postscale.attachment_from_file or pass base64 attachment objects directly:
attachment = Postscale.attachment_from_file(
"./invoice-1234.pdf",
"application/pdf"
)
result = postscale.emails.send(
from: "billing@yourapp.com",
to: ["customer@example.com"],
subject: "Invoice #1234",
html_body: "<p>Your invoice is attached.</p>",
attachments: [attachment]
)
if result.error
raise result.error.message
end
Attachment helpers enforce the documented client-side limits before sending: 10 attachments, 25 MB per attachment, and 50 MB total decoded attachment payload.
Ruby Webhook Verification
The Ruby SDK includes the same verifier for X-Postscale-Signature headers:
result = Postscale.verify_webhook_signature(
request.raw_post,
request.get_header("HTTP_X_POSTSCALE_SIGNATURE"),
[current_secret, previous_secret]
)
unless result.valid?
return head :unauthorized
end
PHP SDK
Install the official Composer package:
composer require postscale/postscale-php
The package supports PHP 8.1+ and uses Guzzle with PSR-compatible HTTP
interfaces. It keeps Postscale's API-native snake_case fields so SDK calls,
REST examples, and the OpenAPI schema stay aligned.
<?php
use Postscale\Postscale;
$postscale = Postscale::client(getenv("POSTSCALE_API_KEY"));
$result = $postscale->emails->send([
"from" => "hello@yourapp.com",
"to" => ["user@example.com"],
"subject" => "Welcome!",
"html_body" => "<h1>Hello World</h1>",
]);
if ($result->error !== null) {
throw new RuntimeException($result->error->message);
}
echo "Email sent: " . $result->data["message_id"];
The package is published at
postscale/postscale-php,
and the source is available at
postscale/postscale-php. Use it
only from trusted server-side code. Do not expose API keys in browser bundles.
PHP Configuration
By default, the SDK reads POSTSCALE_API_KEY and POSTSCALE_BASE_URL from the
environment. You can pass an API key, timeout, retry count, custom headers, or a
custom PSR-18 transport explicitly when needed.
use Postscale\Postscale;
$postscale = Postscale::client("ps_live_your_api_key", [
"timeout" => 30,
"max_retries" => 3,
"headers" => [],
]);
PHP Error Handling
PHP resource methods return the same result shape as the Node, Python, and Ruby SDKs:
$result = $postscale->emails->send([
"from" => "hello@yourapp.com",
"to" => ["user@example.com"],
"subject" => "Welcome!",
"html_body" => "<h1>Hello World</h1>",
]);
if ($result->error !== null) {
error_log($result->error->code . ": " . $result->error->message);
error_log($result->error->requestId ?? "");
exit(1);
}
echo $result->data["status"];
The SDK retries retryable idempotent requests, honors Retry-After and
retry_after_seconds, and does not automatically retry non-idempotent email
sends.
PHP Attachments
Use Attachment::fromFile or pass base64 attachment objects directly:
use Postscale\Attachment;
$attachment = Attachment::fromFile(
"./invoice-1234.pdf",
"application/pdf",
);
$result = $postscale->emails->send([
"from" => "billing@yourapp.com",
"to" => ["customer@example.com"],
"subject" => "Invoice #1234",
"html_body" => "<p>Your invoice is attached.</p>",
"attachments" => [$attachment],
]);
if ($result->error !== null) {
throw new RuntimeException($result->error->message);
}
Attachment helpers enforce the documented client-side limits before sending: 10 attachments, 25 MB per attachment, and 50 MB total decoded attachment payload.
PHP Framework Examples
Laravel:
use Illuminate\Http\JsonResponse;
use Postscale\Postscale;
Route::post("/send-welcome", function (): JsonResponse {
$postscale = Postscale::client(config("services.postscale.key"));
$result = $postscale->emails->send([
"from" => "hello@yourapp.com",
"to" => ["user@example.com"],
"subject" => "Welcome!",
"html_body" => "<h1>Hello World</h1>",
]);
if ($result->error !== null) {
return response()->json(
["error" => $result->error->message],
$result->error->statusCode ?? 500,
);
}
return response()->json($result->data);
});
Symfony:
use Postscale\Postscale;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
final class WelcomeEmailController
{
#[Route("/send-welcome", methods: ["POST"])]
public function __invoke(): JsonResponse
{
$postscale = Postscale::client($_ENV["POSTSCALE_API_KEY"]);
$result = $postscale->emails->send([
"from" => "hello@yourapp.com",
"to" => ["user@example.com"],
"subject" => "Welcome!",
"html_body" => "<h1>Hello World</h1>",
]);
if ($result->error !== null) {
return new JsonResponse(
["error" => $result->error->message],
$result->error->statusCode ?? 500,
);
}
return new JsonResponse($result->data);
}
}
Slim:
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Postscale\Postscale;
$app->post("/send-welcome", function (Request $request, Response $response) {
$postscale = Postscale::client();
$result = $postscale->emails->send([
"from" => "hello@yourapp.com",
"to" => ["user@example.com"],
"subject" => "Welcome!",
"html_body" => "<h1>Hello World</h1>",
]);
$response->getBody()->write(json_encode(
$result->error !== null
? ["error" => $result->error->message]
: $result->data,
JSON_THROW_ON_ERROR,
));
$status = $result->error !== null
? ($result->error->statusCode ?? 500)
: 200;
return $response->withHeader("Content-Type", "application/json")
->withStatus($status);
});
PHP Webhook Verification
The PHP SDK includes the same verifier for X-Postscale-Signature headers:
use Postscale\Webhook;
$result = Webhook::verifySignature(
$rawBody,
$_SERVER["HTTP_X_POSTSCALE_SIGNATURE"] ?? null,
[$currentSecret, $previousSecret],
);
if (!$result->valid()) {
http_response_code(401);
exit;
}
OpenAPI
The public OpenAPI 3.0 specification is available at
/openapi.yaml if you want to generate a typed client or
import the API into Postman, Insomnia, or similar tooling.
REST and HTTP Clients
If you are not using an official SDK, call the REST API directly. All examples use the same public API surface as the SDKs.
Send with curl
curl -X POST https://api.postscale.io/v1/send \
-H "Authorization: Bearer ps_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@yourapp.com",
"to": ["user@example.com"],
"subject": "Welcome!",
"html_body": "<h1>Hello World</h1>"
}'
Send with fetch
const response = await fetch("https://api.postscale.io/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POSTSCALE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "hello@yourapp.com",
to: ["user@example.com"],
subject: "Welcome!",
html_body: "<h1>Hello World</h1>",
}),
});
if (!response.ok) {
throw new Error(`Postscale send failed: ${response.status}`);
}
const result = await response.json();
console.log("Email sent:", result.message_id);
Python requests example
If you prefer not to install the SDK, use a standard HTTP client such as
requests:
import os
import requests
response = requests.post(
"https://api.postscale.io/v1/send",
headers={
"Authorization": f"Bearer {os.environ['POSTSCALE_API_KEY']}",
"Content-Type": "application/json",
},
json={
"from": "hello@yourapp.com",
"to": ["user@example.com"],
"subject": "Welcome!",
"html_body": "<h1>Hello World</h1>",
},
timeout=30,
)
response.raise_for_status()
print("Email sent:", response.json()["message_id"])
Go example
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
func main() {
body, _ := json.Marshal(map[string]any{
"from": "hello@yourapp.com",
"to": []string{"user@example.com"},
"subject": "Welcome!",
"html_body": "<h1>Hello World</h1>",
})
req, _ := http.NewRequest("POST", "https://api.postscale.io/v1/send", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("POSTSCALE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
panic(fmt.Sprintf("Postscale send failed: %s", resp.Status))
}
}
Client configuration
Use these defaults when building your own wrapper:
| Option | Recommendation |
|---|---|
| Base URL | https://api.postscale.io |
| Authentication | Authorization: Bearer <api_key> |
| Content type | application/json |
| Timeout | 30 seconds |
| Retries | Retry transient 429 and 5xx responses with exponential backoff for idempotent requests only |
Webhooks
Webhook signature verification uses standard HMAC code. Node.js, Python, Ruby,
and PHP users can use the SDK helpers above; other languages can use the raw
verification algorithm in the Webhooks guide,
plus details on the timestamped t=...,v1=... format and secret rotation.