{
"openapi": "3.1.0",
"info": {
"title": "Plunk API",
"description": "Open-source email platform API for transactional emails, campaigns, and marketing automation",
"version": "1.0.0",
"contact": {
"name": "Plunk Support",
"url": "https://www.useplunk.com"
}
},
"servers": [
{
"url": "https://next-api.useplunk.com",
"description": "Production server"
}
],
"security": [
{
"ApiKeyAuth": []
}
],
"components": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API Key",
"description": "API Key authentication. **Secret keys (sk_*)** are required for all endpoints except `/v1/track`. **Public keys (pk_*)** only work with the `/v1/track` endpoint for client-side event tracking. The project is automatically derived from the key."
}
},
"schemas": {
"Contact": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique contact identifier"
},
"email": {
"type": "string",
"format": "email",
"description": "Contact email address"
},
"subscribed": {
"type": "boolean",
"description": "Subscription status"
},
"data": {
"type": "object",
"description": "Custom contact data fields",
"additionalProperties": true
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"Template": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"subject": {
"type": "string"
},
"body": {
"type": "string"
},
"type": {
"type": "string",
"enum": ["TRANSACTIONAL", "MARKETING"]
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"Campaign": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"subject": {
"type": "string"
},
"type": {
"type": "string",
"enum": ["ALL", "SEGMENT", "FILTERED"]
},
"status": {
"type": "string",
"enum": ["DRAFT", "SCHEDULED", "SENDING", "SENT"]
},
"scheduledAt": {
"type": "string",
"format": "date-time",
"nullable": true
}
}
},
"Segment": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"filters": {
"type": "object"
},
"trackMembership": {
"type": "boolean"
},
"memberCount": {
"type": "integer"
}
}
},
"Error": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"time": {
"type": "integer"
}
}
}
},
"parameters": {
"Limit": {
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"description": "Maximum items per page"
},
"Cursor": {
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
},
"description": "Pagination cursor"
}
}
},
"paths": {
"/v1/send": {
"post": {
"tags": ["Public API"],
"summary": "Send transactional email",
"description": "Send a transactional email via the public API. Automatically creates or updates the recipient contact.\n\n**Required content:** either a `template` ID, **or** both `subject` and `body`. Template fields can be overridden by explicit request fields.\n\n**Sender:** `from` is required unless using a template that already has a `from` configured. The sender's domain must be verified.\n\n**Multiple recipients:** when `to` is an array, each recipient is processed sequentially with its own contact upsert and rendered email — there is no batch-send semantics. Sending is always immediate; for scheduled sends, use a Campaign.\n\n**Attachments:** up to 10 attachments per email and 10 MB total by default. The total message size cannot exceed 40 MB.",
"operationId": "sendEmail",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["to"],
"properties": {
"to": {
"oneOf": [
{
"type": "string",
"format": "email",
"description": "Simple email address"
},
{
"type": "object",
"required": ["email"],
"properties": {
"name": {
"type": "string",
"description": "Recipient display name"
},
"email": {
"type": "string",
"format": "email",
"description": "Recipient email address"
}
},
"description": "Recipient with name and email"
},
{
"type": "array",
"items": {
"oneOf": [
{
"type": "string",
"format": "email"
},
{
"type": "object",
"required": ["email"],
"properties": {
"name": {
"type": "string",
"description": "Recipient display name"
},
"email": {
"type": "string",
"format": "email",
"description": "Recipient email address"
}
}
}
]
},
"description": "Array of recipients (strings or objects)"
}
],
"description": "Recipient email(s). Can be a string, an object with {name, email}, or an array of either."
},
"subject": {
"type": "string",
"minLength": 1,
"maxLength": 998,
"description": "Email subject. Required if no `template` is provided. Cannot contain newline characters."
},
"body": {
"type": "string",
"minLength": 1,
"description": "Email body (HTML). Required if no `template` is provided."
},
"template": {
"type": "string",
"description": "Template ID to use for this email. When provided, uses the template's subject, body, from, and reply-to settings. You can override these by explicitly providing subject, body, from, or reply fields in the request. Template variables are populated from the data field."
},
"from": {
"oneOf": [
{
"type": "string",
"format": "email",
"description": "Simple email address"
},
{
"type": "object",
"required": ["email"],
"properties": {
"name": {
"type": "string",
"description": "Sender display name"
},
"email": {
"type": "string",
"format": "email",
"description": "Sender email address"
}
},
"description": "Sender with name and email"
}
],
"description": "Sender email address (requires verified domain). Required unless using a template that has a 'from' address configured. Can be a string (e.g., 'hello@example.com') or an object with {name, email} (e.g., {name: 'My App', email: 'hello@example.com'})."
},
"name": {
"type": "string",
"description": "**Deprecated.** Sender display name. Prefer `from: { name, email }`. Used only as a fallback when `from` is a string and no name is set there."
},
"subscribed": {
"type": "boolean",
"description": "Subscription state to apply to the recipient. For **new** contacts, defaults to `false` on `/v1/send`. For **existing** contacts, omitting this preserves their current state — pass `true` or `false` to explicitly change it. A change emits `contact.subscribed` or `contact.unsubscribed`."
},
"data": {
"type": "object",
"additionalProperties": true,
"description": "Variables for template rendering and contact data updates. Each value can be:\n- A primitive (string, number, boolean) — saved on the contact and available as a template variable.\n- `null` — deletes the field from the contact.\n- An empty string — skipped (does not overwrite existing data).\n- An object `{ value, persistent: false }` — used for this send only, not stored on the contact (good for one-shot password reset codes, magic links).\n\nReserved keys (`id`, `plunk_id`, `plunk_email`, `email`, `unsubscribeUrl`, `subscribeUrl`, `manageUrl`) are silently filtered out."
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Custom email headers. Header names cannot contain `\\r\\n`. Header values are limited to 998 characters and cannot contain `\\r\\n` (header injection is rejected)."
},
"reply": {
"type": "string",
"format": "email",
"description": "Reply-to address."
},
"attachments": {
"type": "array",
"description": "Email attachments. Default cap: 10 attachments and 10 MB total. The full message size cannot exceed 40 MB.",
"maxItems": 10,
"items": {
"type": "object",
"required": ["filename", "content", "contentType"],
"properties": {
"filename": {
"type": "string",
"maxLength": 255,
"description": "Attachment filename. Cannot contain newline or quote characters."
},
"content": {
"type": "string",
"description": "Base64-encoded file content."
},
"contentType": {
"type": "string",
"maxLength": 255,
"description": "MIME type (e.g., `application/pdf`, `image/png`)."
},
"contentId": {
"type": "string",
"description": "Content-ID for inline images. Required when `disposition` is `inline`. Reference the image in the email body via ``."
},
"disposition": {
"type": "string",
"enum": ["attachment", "inline"],
"default": "attachment",
"description": "Use `inline` together with `contentId` to embed images in the body. Use `attachment` (the default) for downloadable files."
}
}
}
}
}
},
"examples": {
"simple": {
"summary": "Simple transactional email",
"value": {
"to": "user@example.com",
"subject": "Password Reset Request",
"body": "
Click the link to reset: {{resetLink}}
", "data": { "resetLink": "https://example.com/reset/abc123" } } }, "withNames": { "summary": "Email with recipient and sender names", "value": { "to": { "name": "Jane Doe", "email": "jane@example.com" }, "from": { "name": "My Company", "email": "hello@mycompany.com" }, "subject": "Welcome to Our Service", "body": "We're glad to have you.
", "data": { "name": "Jane" } } }, "multipleRecipients": { "summary": "Multiple recipients with names", "value": { "to": [ { "name": "Jane Doe", "email": "jane@example.com" }, { "name": "John Smith", "email": "john@example.com" } ], "from": { "name": "Newsletter", "email": "news@mycompany.com" }, "subject": "Monthly Update", "body": "Please find your invoice attached.
", "attachments": [ { "filename": "invoice.pdf", "content": "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9UeXBlL...", "contentType": "application/pdf" } ] } } } } } }, "responses": { "200": { "description": "Email queued successfully", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" }, "data": { "type": "object", "properties": { "emails": { "type": "array", "items": { "type": "object", "properties": { "contact": { "type": "object", "properties": { "id": { "type": "string" }, "email": { "type": "string" } } }, "email": { "type": "string", "description": "Plunk email record ID. Use this to correlate webhook events (which include this ID as 'emailId' in the event data) with your send requests." } } } }, "timestamp": { "type": "string", "format": "date-time" } } } } }, "example": { "success": true, "data": { "emails": [ { "contact": { "id": "cnt_abc123", "email": "user@example.com" }, "email": "ac32f08e-c6b9-45d3-9824-a73dff1e3bbf" } ], "timestamp": "2025-01-15T10:30:00.000Z" } } } } }, "400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "description": "Unauthorized", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/v1/track": { "post": { "tags": ["Public API"], "summary": "Track event", "description": "Track an event for a contact. Automatically creates or upserts the contact, then records the event. Tracked events can be used as workflow triggers, segment filters, and audience filters.\n\n**Reserved event names** (rejected with `VALIDATION_ERROR` and code `reserved_event`): anything matching `email.*`, `contact.subscribed`, `contact.unsubscribed`, `segment.