API reference · Emails
Send an email
POST
https://api.pidgely.com/emailsQueues one email for delivery and returns its id. The from domain must be verified and allowed for the API key. Up to 50 unique recipients across to, cc and bcc; a text part is generated from html when you omit text.
scheduled_at accepts ISO 8601 or natural language such as in 1 hour or tomorrow at 9am (UTC), up to 30 days ahead. Attachments take base64 content or a remote path. Send an Idempotency-Key to retry safely. See Sending email for every option.
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. |
Body parameters
JSON object.
| 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.emails.send({
from: 'Acme <onboarding@acme.com>',
to: ['delivered@example.com'],
subject: 'Welcome to Acme',
html: '<p>Thanks for signing up!</p>',
tags: [
{
name: 'category',
value: 'welcome',
},
],
}, {
idempotencyKey: 'welcome-user-123',
});
console.log(data ?? error);import os
import pidgely
pidgely.api_key = os.environ["PIDGELY_API_KEY"]
result = pidgely.Emails.send({
"from": "Acme <onboarding@acme.com>",
"to": ["delivered@example.com"],
"subject": "Welcome to Acme",
"html": "<p>Thanks for signing up!</p>",
"tags": [
{
"name": "category",
"value": "welcome",
},
],
}, {
"idempotency_key": "welcome-user-123",
})
print(result)<?php
require __DIR__ . '/vendor/autoload.php';
$pidgely = Pidgely::client(getenv('PIDGELY_API_KEY'));
$result = $pidgely->emails->send([
'from' => 'Acme <onboarding@acme.com>',
'to' => ['delivered@example.com'],
'subject' => 'Welcome to Acme',
'html' => '<p>Thanks for signing up!</p>',
'tags' => [
[
'name' => 'category',
'value' => 'welcome',
],
],
], [
'idempotency_key' => 'welcome-user-123',
]);
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.Emails.Send(ctx, &pidgely.SendEmailRequest{
From: "Acme <onboarding@acme.com>",
To: []string{"delivered@example.com"},
Subject: "Welcome to Acme",
Html: "<p>Thanks for signing up!</p>",
Tags: []pidgely.Tag{
{
Name: "category",
Value: "welcome",
},
},
}, pidgely.WithIdempotencyKey("welcome-user-123"))
if err != nil {
panic(err)
}
fmt.Println(result)
}require "pidgely"
Pidgely.api_key = ENV["PIDGELY_API_KEY"]
result = Pidgely::Emails.send({
from: "Acme <onboarding@acme.com>",
to: ["delivered@example.com"],
subject: "Welcome to Acme",
html: "<p>Thanks for signing up!</p>",
tags: [
{
name: "category",
value: "welcome"
}
]
}, {
idempotency_key: "welcome-user-123"
})
puts resultcurl -X POST 'https://api.pidgely.com/emails' \
-H "Authorization: Bearer $PIDGELY_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: welcome-user-123' \
-d '{
"from": "Acme <onboarding@acme.com>",
"to": [
"delivered@example.com"
],
"subject": "Welcome to Acme",
"html": "<p>Thanks for signing up!</p>",
"tags": [
{
"name": "category",
"value": "welcome"
}
]
}'Response
200 OK
{
"id": "0199d8a2-6f3c-7c51-9e2a-3b1f0c6d4e21"
}Response fields
| Name | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique id (UUID). UUID. |
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. |
| 403 | validation_error |
The from domain is not verified, the key is restricted to another domain, or sending is paused for the account. |
| 409 | invalid_idempotent_request |
The Idempotency-Key was already used with a different request body. |
| 400 | invalid_idempotency_key |
The Idempotency-Key is longer than 256 characters. |
| 422 | invalid_attachment |
An attachment has neither content nor path. |
| 429 | daily_quota_exceeded |
The daily sending quota is used up (UTC day). |
| 429 | monthly_quota_exceeded |
The monthly sending quota is used up. |