SendPigeon Documentation
Everything you need to send transactional emails. SDKs for Node.js, Python, Go, PHP and REST API.
SDKs
Full reference for all official SDKs.
Send Options
| Option | Type | Description |
|---|---|---|
| from* | string | Sender email. Domain must be verified. |
| to* | string | string[] | Recipient email(s). |
| subject | string | Email subject. Required unless using templateId. |
| html | string | HTML body content. |
| text | string | Plain text body. |
| cc | string | string[] | CC recipients. |
| bcc | string | string[] | BCC recipients. |
| replyTo | string | Reply-to address. |
| templateId | string | Template ID to use. |
| variables | object | Variables for template substitution. |
| attachments | Attachment[] | File attachments. Max 7MB each, 25MB total. |
| tags | string[] | Tags for filtering/analytics. Max 5 tags. |
| metadata | object | Custom key-value pairs. Returned in webhooks. |
| headers | object | Custom headers (X-Priority, List-Unsubscribe, etc). |
| scheduledAt | string | ISO 8601 datetime to send. Max 30 days ahead. |
Attachments
Attach files via base64 content or URL (fetched server-side).
// Base64 contentconst { error } = await pigeon.send({ from: "hello@yourdomain.com", to: "user@example.com", subject: "Your invoice", html: "<p>See attached invoice.</p>", attachments: [{ filename: "invoice.pdf", content: fs.readFileSync("invoice.pdf").toString("base64") }]});Limits: 7MB per file, 25MB total. Blocked: .exe, .bat, .sh, .dll and other executables.
Scheduled Sending
Schedule emails up to 30 days in advance. Cancel before send time.
// Schedule for laterconst { data } = await pigeon.send({ from: "hello@yourdomain.com", to: "user@example.com", subject: "Reminder", html: "<p>Don't forget!</p>", scheduledAt: "2025-06-15T10:00:00Z"});// { id: "em_abc123", status: "scheduled", scheduledAt: "..." }// Cancel scheduled emailawait pigeon.emails.cancel(data.id);Tags & Metadata
Track emails with tags and metadata. Both are returned in webhooks and email details.
const { data } = await pigeon.send({ from: "hello@yourdomain.com", to: "user@example.com", subject: "Order confirmed", html: "<p>Your order is confirmed.</p>", tags: ["order", "confirmation"], metadata: { orderId: "12345", userId: "abc" }, headers: { "X-Priority": "1" }});Get Email Status
Check delivery status, tags, and metadata of a sent email.
const { data } = await pigeon.emails.get("em_abc123");console.log(data.status); // "delivered"console.log(data.tags); // ["order", "confirmation"]console.log(data.metadata); // { orderId: "12345" }Templates
Templates use {{variable}} syntax. Create them in the dashboard or via SDK.
// Create a templateconst { data: template } = await pigeon.templates.create({ name: "welcome-email", subject: "Welcome {{name}}!", html: "<p>Hello {{name}}, welcome to {{company}}!</p>"});// Send with templateconst { error } = await pigeon.send({ from: "hello@yourdomain.com", to: "user@example.com", templateId: template.id, variables: { name: "Johan", company: "Acme" }});Webhooks
Receive real-time notifications for email events. Configure your webhook URL in the dashboard settings.
| Event | Description |
|---|---|
| email.delivered | Email successfully delivered to recipient |
| email.bounced | Email bounced (hard or soft bounce) |
| email.complained | Recipient marked email as spam |
| email.opened | Recipient opened the email (requires tracking enabled) |
| email.clicked | Recipient clicked a link (requires tracking enabled) |
// Webhook payload{ "event": "email.delivered", "timestamp": "2025-01-15T10:30:00Z", "data": { "emailId": "em_abc123", "toAddress": "user@example.com", "fromAddress": "hello@yourdomain.com", "subject": "Welcome!" }}// For bounces, includes bounceType// For complaints, includes complaintTypeWebhook Verification
Verify webhook signatures using the SDK helper.
import { verifyWebhook } from "sendpigeon";app.post("/webhook", async (req, res) => { const result = await verifyWebhook({ payload: req.body, signature: req.headers["x-webhook-signature"], timestamp: req.headers["x-webhook-timestamp"], secret: process.env.WEBHOOK_SECRET, }); if (!result.valid) { return res.status(400).json({ error: result.error }); } const { event, data } = result.payload; // Handle event... res.sendStatus(200);});Using with React Email
Build emails with React components using React Email, then render to HTML before sending.
import { render } from "@react-email/render";import { SendPigeon } from "sendpigeon";import { WelcomeEmail } from "./emails/welcome";const pigeon = new SendPigeon("sp_live_your_api_key");const html = await render(<WelcomeEmail name="Johan" />);await pigeon.send({ from: "hello@yourdomain.com", to: "user@example.com", subject: "Welcome!", html,});Configuration
Customize client behavior with options.
const pigeon = new SendPigeon("sp_live_your_api_key", { timeout: 30000, // request timeout in ms (default: 30s) maxRetries: 2, // retry on 429/5xx (default: 2, max: 5) debug: true, // log requests/responses to console});Retries use exponential backoff (500ms, 1s, 2s, 4s, 8s) and respect Retry-After headers.
Batch Sending
Send up to 100 emails in a single request. Each email is processed independently.
const { data, error } = await pigeon.sendBatch([ { from: "hello@yourdomain.com", to: "user1@example.com", subject: "Hello User 1", html: "<p>Welcome!</p>", }, { from: "hello@yourdomain.com", to: "user2@example.com", subject: "Hello User 2", html: "<p>Welcome!</p>", },]);// Check resultsconsole.log(data.summary); // { total: 2, sent: 2, failed: 0 }for (const result of data.data) { if (result.status === "sent") { console.log(`Email ${result.index} sent: ${result.id}`); } else { console.log(`Email ${result.index} failed: ${result.error.message}`); }}Domain Management
Manage sending domains programmatically.
// List domainsconst { data: domains } = await pigeon.domains.list();// Add a domainconst { data: domain } = await pigeon.domains.create({ name: "mail.example.com"});// Get domain with DNS recordsconst { data: details } = await pigeon.domains.get(domain.id);console.log(details.dnsRecords); // DKIM, SPF, DMARC records to add// Verify DNS is configuredconst { data: result } = await pigeon.domains.verify(domain.id);console.log(result.status); // "verified" | "pending" | "failed"// Delete domainawait pigeon.domains.delete(domain.id);API Key Management
Create and manage API keys with specific permissions.
// List API keysconst { data: keys } = await pigeon.apiKeys.list();// Create a key with optionsconst { data: newKey } = await pigeon.apiKeys.create({ name: "Production Server", mode: "live", // "live" | "test" permission: "sending", // "sending" | "full_access" expiresAt: "2025-12-31", // optional expiration domainId: "dom_abc123", // optional domain restriction});console.log(newKey.key); // Save this - only shown once!// Delete a keyawait pigeon.apiKeys.delete(newKey.id);Local Development
Catch emails locally during development with @sendpigeon-sdk/cli. No real emails sent.
# Terminal 1: Start the dev servernpx @sendpigeon-sdk/cli dev# Terminal 2: Run your app with dev mode enabledSENDPIGEON_DEV=true npm run devWhen SENDPIGEON_DEV=true is set, the SDK routes to localhost:4100 instead of production.
// The SDK auto-detects dev modeconst client = new SendPigeon("sp_test_xxx");// When SENDPIGEON_DEV=true, this goes to localhost:4100await client.send({ from: "hello@yourdomain.com", to: "user@example.com", subject: "Test", html: "<p>Caught locally!</p>"});// You'll see in your logs:// [SendPigeon] Dev mode → http://localhost:4100UI: View caught emails at http://localhost:4100. Learn more →