Plunk Public API

Public API endpoints for sending emails and tracking events

Operations 3

POST /v1/send Send transactional email #
POST /v1/track Track event #
POST /v1/verify Verify email address #

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-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.<slug>.entry`, `segment.<slug>.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:
  responses:
    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'
    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'
    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'
  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
  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: {}