API reference · Emails
Send a batch of emails
POST
https://api.pidgely.com/emails/batchSends 1 to 100 emails in one request. Each item has the same shape as Send an email; attachments, scheduled_at and template are allowed.
By default the batch is atomic (x-batch-validation: strict): the first invalid item fails the whole request and the error names it, for example [3].to. With x-batch-validation: permissive, valid items are queued and invalid ones are reported in errors with their index. A batch that would exceed the daily quota is rejected as a whole.
Authorization: Bearer pdg_…. Any API key (full_access or sending_access).
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Idempotency-Key |
string | No | Replays the stored response for 24 hours; a different body gets 409. Up to 256 characters. |
x-batch-validation |
string | No | strict (default): all or nothing; permissive: valid items are sent, invalid ones reported in errors. One of strict, permissive. |
Body parameters
A JSON array of objects (1–100 items). Each item:
| Name | Type | Required | Description |
|---|---|---|---|
from |
string | No | Name <addr@verified-domain>; may come from the template. |
to |
string | string[] | Yes | Recipient address (a@x.com or Name <a@x.com>) or a list of them. Up to 50 unique recipients across to, cc and bcc. |
cc |
string | string[] | No | Carbon-copy recipients. Count toward the 50-recipient limit. |
bcc |
string | string[] | No | Blind carbon-copy recipients. Count toward the 50-recipient limit. |
reply_to |
string | string[] | No | Reply-To address or list of addresses. |
subject |
string | No | Subject line, up to 998 bytes. Required unless a template provides it. |
html |
string | No | HTML body. html or text is required (unless you send with a template). |
text |
string | No | Generated from html when omitted. |
headers |
object (map of string) | No | Custom headers. Platform-managed headers (From, To, Subject, Message-ID, DKIM-Signature, Return-Path, Reply-To, …) are rejected; values cannot contain line breaks. |
attachments |
object[] | No | Files to attach. All attachments share a 40 MiB budget (decoded). Executable types (.exe, .js, .bat, …) are blocked. |
attachments[].filename |
string | No | File name shown to the recipient. Defaults to the last segment of path. |
attachments[].content |
string | No | Base64 content. Use either content or path. |
attachments[].path |
string | No | http(s) URL downloaded at send time (SSRF-guarded, 40MB budget, no redirects). |
attachments[].content_type |
string | No | MIME type. Guessed from the file extension when omitted. |
attachments[].content_id |
string | No | Makes the attachment inline (cid: in HTML). |
tags |
object[] | No | Name/value pairs attached to the email and returned in webhooks. |
tags[].name |
string | Yes | Display name. Pattern: ^[A-Za-z0-9_-]{1,256}$. |
tags[].value |
string | Yes | Pattern: ^[A-Za-z0-9_-]{1,256}$. |
scheduled_at |
string | No | ISO 8601 or natural language (in 1 hour, tomorrow at 9am); within 30 days. |
template |
object | No | — |
template.id |
string | Yes | Template id or alias (published version is used). |
template.variables |
object (map of string | number) | No | Values for the template variables: strings up to 2,000 characters or numbers. |
topic_id |
string | No | Not supported yet; a non-empty value is rejected. |
stream |
string | No | Pidgely extension: transactional (default) or broadcast (adds RFC 8058 unsubscribe). One of transactional, broadcast. |
Request
import { Pidgely } from 'pidgely';
const pidgely = new Pidgely(process.env.PIDGELY_API_KEY);
const { data, error } = await pidgely.batch.send([
{
from: 'Acme <onboarding@acme.com>',
to: ['alice@example.com'],
subject: 'Hi Alice',
html: '<p>Welcome, Alice!</p>',
},
{
from: 'Acme <onboarding@acme.com>',
to: ['bob@example.com'],
subject: 'Hi Bob',
html: '<p>Welcome, Bob!</p>',
},
], {
batchValidation: 'permissive',
});
console.log(data ?? error);import os
import pidgely
pidgely.api_key = os.environ["PIDGELY_API_KEY"]
result = pidgely.Batch.send([
{
"from": "Acme <onboarding@acme.com>",
"to": ["alice@example.com"],
"subject": "Hi Alice",
"html": "<p>Welcome, Alice!</p>",
},
{
"from": "Acme <onboarding@acme.com>",
"to": ["bob@example.com"],
"subject": "Hi Bob",
"html": "<p>Welcome, Bob!</p>",
},
], {
"batch_validation": "permissive",
})
print(result)<?php
require __DIR__ . '/vendor/autoload.php';
$pidgely = Pidgely::client(getenv('PIDGELY_API_KEY'));
$result = $pidgely->batch->send([
[
'from' => 'Acme <onboarding@acme.com>',
'to' => ['alice@example.com'],
'subject' => 'Hi Alice',
'html' => '<p>Welcome, Alice!</p>',
],
[
'from' => 'Acme <onboarding@acme.com>',
'to' => ['bob@example.com'],
'subject' => 'Hi Bob',
'html' => '<p>Welcome, Bob!</p>',
],
], [
'batch_validation' => 'permissive',
]);
var_dump($result);package main
import (
"context"
"fmt"
"os"
"github.com/pidgely/pidgely-go"
)
func main() {
ctx := context.Background()
client := pidgely.NewClient(os.Getenv("PIDGELY_API_KEY"))
result, err := client.Batch.Send(ctx, []pidgely.SendEmailRequest{
{
From: "Acme <onboarding@acme.com>",
To: []string{"alice@example.com"},
Subject: "Hi Alice",
Html: "<p>Welcome, Alice!</p>",
},
{
From: "Acme <onboarding@acme.com>",
To: []string{"bob@example.com"},
Subject: "Hi Bob",
Html: "<p>Welcome, Bob!</p>",
},
}, pidgely.WithBatchValidation(pidgely.BatchPermissive))
if err != nil {
panic(err)
}
fmt.Println(result)
}require "pidgely"
Pidgely.api_key = ENV["PIDGELY_API_KEY"]
result = Pidgely::Batch.send([
{
from: "Acme <onboarding@acme.com>",
to: ["alice@example.com"],
subject: "Hi Alice",
html: "<p>Welcome, Alice!</p>"
},
{
from: "Acme <onboarding@acme.com>",
to: ["bob@example.com"],
subject: "Hi Bob",
html: "<p>Welcome, Bob!</p>"
}
], {
batch_validation: "permissive"
})
puts resultcurl -X POST 'https://api.pidgely.com/emails/batch' \
-H "Authorization: Bearer $PIDGELY_API_KEY" \
-H 'Content-Type: application/json' \
-H 'x-batch-validation: permissive' \
-d '[
{
"from": "Acme <onboarding@acme.com>",
"to": [
"alice@example.com"
],
"subject": "Hi Alice",
"html": "<p>Welcome, Alice!</p>"
},
{
"from": "Acme <onboarding@acme.com>",
"to": [
"bob@example.com"
],
"subject": "Hi Bob",
"html": "<p>Welcome, Bob!</p>"
}
]'Response
200 OK
{
"data": [
{
"id": "0199d8a2-6f3c-7c51-9e2a-3b1f0c6d4e21"
},
{
"id": "0199d8a2-7a10-7d02-8b4e-51c2a9f0e7b3"
}
],
"errors": []
}Response fields
| Name | Type | Required | Description |
|---|---|---|---|
data |
object[] | Yes | Items of this page. |
data[].id |
string | Yes | Unique id (UUID). UUID. |
errors |
object[] | No | — |
errors[].index |
integer | No | — |
errors[].message |
string | No | — |
Errors
Errors have the shape {"statusCode", "name", "message"}. See Errors for the full list.
| Status | Name | When |
|---|---|---|
| 401 | missing_api_key, restricted_api_key |
No Authorization: Bearer header, or a sending_access key called an endpoint that needs full_access. |
| 403 | invalid_api_key, validation_error |
The API key is invalid or revoked, or the request is not allowed for this account (unverified domain, plan limit, paused account). |
| 404 | not_found |
The resource does not exist in this account (or the id is not a valid UUID). |
| 422 | validation_error |
A field is missing or invalid; message names it. |
| 429 | rate_limit_exceeded |
Too many requests per second. Wait for Retry-After seconds. |
| 409 | invalid_idempotent_request |
The Idempotency-Key was already used with a different request body. |