Plunk Public API

Public API endpoints for sending emails and tracking events

Operations 3

Each operation below carries the questions people ask an LLM about it and the instructions they give an agent to run it. Generated by API Evangelist overlay

POST /v1/send Send a transactional email · Send transactional email #
Ask an LLM
“How do I send a password reset email through Plunk?”
“Can I send a transactional email using a saved template instead of a subject and body?”
Tell an agent
Email {to} with subject {subject} and body {body}.
Send template {template} to {to} with variables {data}.
POST /v1/track Track an event for a contact · Track event #
Ask an LLM
“How do I log a custom event like a purchase against a contact?”
“Which event names are reserved and will be rejected?”
Tell an agent
Track event {event} for contact {email}.
Record {event} for {email} with data {data}.
POST /v1/verify Verify an email address · Verify email address #
Ask an LLM
“Can I check whether an email address is valid before I add it?”
“Is there a way to detect disposable email addresses or typos in a domain?”
Tell an agent
Verify the email address {email}.
Check whether {email} has valid MX records and is not disposable.

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/plunk:plunk-public-api-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

plunk-public-api-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Plunk Public 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: []
tags:
- name: Public API
  description: Public API endpoints for sending emails and tracking events
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.


        **Required content:** either a `template` ID, **or** both `subject` and `body`. Template fields can be overridden by explicit request fields.


        **Sender:** `from` is required unless using a template that already has a `from` configured. The sender''s domain must be verified.


        **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.


        **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
                  format: uuid
                  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:

                    - A primitive (string, number, boolean) — saved on the contact and available as a template variable.

                    - `null` — deletes the field from the contact.

                    - An empty string — skipped (does not overwrite existing data).

                    - An object `{ value, persistent: false }` — used for this send only, not stored on the contact (good for one-shot password reset codes, magic links).


                    Reserved 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 `<img src="cid:yourContentId">`.
                      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: '<h1>Reset Your Password</h1><p>Click the link to reset: {{resetLink}}</p>'
                  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: <h1>Welcome {{name}}!</h1><p>We're glad to have you.</p>
                  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: <h1>Hello {{name}}!</h1>
              withTemplate:
                summary: Using a template
                description: Send email using a template. Provide the template ID and any data for template variables. The template's subject, body, from address, and reply-to will be used automatically.
                value:
                  to: user@example.com
                  template: 9c4d5e1f-2a3b-4c5d-8e9f-0a1b2c3d4e5f
                  data:
                    firstName: John
                    lastName: Doe
                    resetCode:
                      value: ABC123
                      persistent: false
              withTemplateOverride:
                summary: Using template with overrides
                description: You can override template values by providing subject, body, from, or reply fields. This example overrides the template's subject line.
                value:
                  to: user@example.com
                  template: 9c4d5e1f-2a3b-4c5d-8e9f-0a1b2c3d4e5f
                  subject: Custom Subject Override
                  data:
                    firstName: Jane
              marketingEmail:
                summary: 'Marketing email (set subscribed: true)'
                value:
                  to: user@example.com
                  subject: Weekly Newsletter
                  body: <h1>This Week's Updates</h1>
                  subscribed: true
              withAttachment:
                summary: Email with PDF attachment
                value:
                  to: user@example.com
                  subject: Your Invoice
                  body: <h1>Invoice Attached</h1><p>Please find your invoice attached.</p>
                  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: Malformed JSON body, or an invalid `Idempotency-Key` header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: The project is disabled and cannot send.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: The `template` ID does not exist in this project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '422':
          $ref: '#/components/responses/ValidationError'
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
  /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.


        **Reserved event names** (rejected with `VALIDATION_ERROR` and code `reserved_event`): anything matching `email.*`, `contact.subscribed`, `contact.unsubscribed`, `segment..entry`, `segment..exit`. These are emitted by Plunk itself.


        **Idempotency**: re-tracking the same event creates a new event record. Send an `Idempotency-Key` header to have a repeated request refused with `409` instead.'
      operationId: trackEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              - event
              properties:
                email:
                  type: string
                  format: email
                  description: Contact email. The contact is auto-created if it doesn't exist.
                event:
                  type: string
                  description: Event name. Cannot match the reserved patterns above.
                subscribed:
                  type: boolean
                  description: Subscription state to apply to the contact. **New** contacts default to subscribed (`true`). **Existing** contacts keep their current state unless you pass an explicit value here. Pass `false` to track an event without resubscribing an unsubscribed contact.
                data:
                  type: object
                  additionalProperties: true
                  description: 'Contact data and one-off event variables. Persistent values (primitives, plain objects) are saved on the contact and become available as template variables. Pass `{ value, persistent: false }` for one-shot variables that should not be stored on the contact (e.g. order IDs, transaction details). `null` deletes a field. Empty strings are ignored. Reserved keys are filtered out — see the contacts concept page.'
            example:
              email: user@example.com
              event: purchase
              data:
                product: Premium Plan
                amount: 99
      responses:
        '200':
          description: Event tracked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      contact:
                        type: string
                        description: Contact ID
                      event:
                        type: string
                        description: Event ID
                      timestamp:
                        type: string
                        format: date-time
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '422':
          $ref: '#/components/responses/ValidationError'
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
  /v1/verify:
    post:
      tags:
      - Public API
      summary: Verify email address
      description: Verify an email address for validity, check if it's from a disposable domain or personal email provider, verify MX records, and detect potential typos with suggestions.
      operationId: verifyEmail
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              properties:
                email:
                  type: string
                  format: email
                  description: Email address to verify
            examples:
              validEmail:
                summary: Valid email address
                value:
                  email: user@gmail.com
              typoEmail:
                summary: Email with potential typo
                value:
                  email: user@gmial.com
              disposableEmail:
                summary: Disposable email address
                value:
                  email: user@tempmail.com
      responses:
        '200':
          description: Email verification completed successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    description: Always true for successful requests
                  data:
                    type: object
                    properties:
                      email:
                        type: string
                        format: email
                        description: Email address that was verified
                      valid:
                        type: boolean
                        description: Whether the email appears to be valid overall
                      isDisposable:
                        type: boolean
                        description: Whether the email is from a disposable/temporary email domain
                      isAlias:
                        type: boolean
                        description: Whether the email is from a forwarding/alias service
                      isTypo:
                        type: boolean
                        description: Whether a potential typo was detected in the email address
                      isPlusAddressed:
                        type: boolean
                        description: Whether the email uses plus addressing (contains a + in the local part)
                      isPersonalEmail:
                        type: boolean
                        description: Whether the email is from a personal/free email provider (Gmail, Hotmail, Yahoo, etc.)
                      domainExists:
                        type: boolean
                        description: Whether the domain exists in DNS (has NS records)
                      hasWebsite:
                        type: boolean
                        description: Whether the domain has a website (has DNS A or AAAA records) - informational only
                      hasMxRecords:
                        type: boolean
                        description: Whether the domain has MX records configured for email delivery
                      suggestedEmail:
                        type: string
                        format: email
                        description: Suggested correction if a typo was detected (optional)
                        nullable: true
                      reasons:
                        type: array
                        items:
                          type: string
                        description: Array of human-readable reasons describing the verification results
                    required:
                    - email
                    - valid
                    - isDisposable
                    - isAlias
                    - isTypo
                    - isPlusAddressed
                    - isPersonalEmail
                    - domainExists
                    - hasWebsite
                    - hasMxRecords
                    - reasons
              examples:
                validEmail:
                  summary: Valid email
                  value:
                    success: true
                    data:
                      email: user@gmail.com
                      valid: true
                      isDisposable: false
                      isAlias: false
                      isTypo: false
                      isPlusAddressed: false
                      isPersonalEmail: true
                      domainExists: true
                      hasWebsite: true
                      hasMxRecords: true
                      reasons:
                      - Email appears to be valid
                typoDetected:
                  summary: Email with typo detected
                  value:
                    success: true
                    data:
                      email: user@gmial.com
                      valid: false
                      isDisposable: false
                      isAlias: false
                      isTypo: true
                      isPlusAddressed: false
                      isPersonalEmail: false
                      domainExists: false
                      hasWebsite: false
                      hasMxRecords: false
                      suggestedEmail: user@gmail.com
                      reasons:
                      - Possible typo detected, did you mean gmail.com?
                      - Domain does not exist (no nameservers found)
                disposableEmail:
                  summary: Disposable email detected
                  value:
                    success: true
                    data:
                      email: user@tempmail.com
                      valid: true
                      isDisposable: true
                      isAlias: false
                      isTypo: false
                      isPlusAddressed: false
                      isPersonalEmail: false
                      domainExists: true
                      hasWebsite: true
                      hasMxRecords: true
                      reasons:
                      - Email appears to be valid
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'
components:
  schemas:
    FieldError:
      type: object
      properties:
        field:
          type: string
          description: Dot-path of the offending field, e.g. `attachments.0.filename`.
        message:
          type: string
        code:
          type: string
          description: Validation issue code, e.g. `invalid_type`, `too_big`, `reserved_event`.
        received:
          description: The value that was received, when available.
    Error:
      type: object
      properties:
        success:
          type: boolean
          enum:
          - false
        error:
          type: object
          properties:
            code:
              type: string
              description: Machine-readable error code, e.g. `VALIDATION_ERROR`, `INVALID_API_KEY`, `IDEMPOTENCY_KEY_REUSED`.
            message:
              type: string
            statusCode:
              type: integer
            requestId:
              type: string
              description: Correlation ID for this request. Include it when contacting support.
            errors:
              type: array
              items:
                $ref: '#/components/schemas/FieldError'
              description: Field-level detail, present on validation failures.
            details:
              type: object
              additionalProperties: true
              description: Additional error context.
            suggestion:
              type: string
              description: Hint for fixing the request.
        timestamp:
          type: string
          format: date-time
  responses:
    ValidationError:
      description: Request body failed schema validation. `error.errors` lists the offending fields.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error:
              code: VALIDATION_ERROR
              message: Request validation failed
              statusCode: 422
              requestId: 8f14e45f-ceea-467a-9575-1f0f38e0b1c2
              errors:
              - field: to
                message: Invalid email
                code: invalid_string
              suggestion: Please check the API documentation for the correct request format.
            timestamp: '2025-01-15T10:30:00.000Z'
    IdempotencyConflict:
      description: Idempotency-Key already used. The request was refused, not performed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error:
              code: IDEMPOTENCY_KEY_REUSED
              message: Idempotency-Key "order-1234-receipt" has already been used
              statusCode: 409
              requestId: 8f14e45f-ceea-467a-9575-1f0f38e0b1c2
              details:
                key: order-1234-receipt
                originalRequest: POST /v1/send
                originalRequestAt: '2025-01-15T10:30:00.000Z'
                originalStatusCode: 200
              suggestion: This Idempotency-Key was already used, so the request was refused rather than performed twice. Generate a new key for a genuinely new request.
            timestamp: '2025-01-15T10:31:00.000Z'
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            success: false
            error:
              code: INVALID_API_KEY
              message: Invalid secret API key. This endpoint requires a secret key (sk_*), not a public key.
              statusCode: 401
              requestId: 8f14e45f-ceea-467a-9575-1f0f38e0b1c2
            timestamp: '2025-01-15T10:30:00.000Z'
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: Optional key that guarantees this request runs at most once. If the key was already used by your project, the request is refused with `409` instead of being performed a second time. Keys are scoped to your project, expire after 24 hours (configurable when self-hosting), and must be 1-255 printable ASCII characters.
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: 'API Key authentication. The project is automatically derived from the key.


        **`/v1/track` requires a public key (`pk_*`)** — it is the one endpoint intended for client-side use, and a secret key is rejected there with `401`.


        **Every other endpoint requires a secret key (`sk_*`)** and rejects public keys with `401`.


        So the two key types are not interchangeable in either direction: pick the key that matches the endpoint you are calling.'
x-ext-urls: {}
x-provenance:
  method: harvested
  publisher: Plunk
  source: https://docs.useplunk.com/openapi.json
  note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
    stamped by stamp-openapi-provenance.py (roadmap#793)