PlunkPlunk
Guides

Receiving emails

Receive incoming email at your verified domain and turn it into events you can drive workflows from

Plunk can receive emails sent to any address at your verified domain, store them in your project, and emit an email.received event you can drive workflows from. This unlocks auto-replies, ticketing, conditional forwarding, and any other "do something when an email arrives" pattern.

What happens when an email arrives

When someone emails an address at your verified domain, Plunk:

  1. Parses the message and stores it as an inbound Email record visible in your project's Activity feed.
  2. Creates the sender as a contact in your project (or updates them if they already exist), subscribed by default.
  3. Tracks an email.received event on the sender contact, which any workflow can listen to.

The HTML body is sanitized before being stored — scripts, iframes, event handlers, and javascript: URIs are stripped. Plain text is preserved as-is when no HTML part is available.

Setup

Verify your domain for sending

Inbound is only enabled on domains that are fully verified for sending (DKIM + SPF + the bounce-feedback MX). Follow the Verifying domains guide first.

Add the inbound MX record

Open your project's Domains tab, expand the verified domain, and look for the Inbound Email section. Plunk shows you the exact MX record value to add to your DNS — copy it from there and add it as an MX record on your domain.

Conflict with existing email

A domain can only have one primary inbound MX target. If you already use Google Workspace, Microsoft 365, or another provider for receiving email on the apex domain, your existing email will break if you switch the MX to point at Plunk. The usual fix is to receive Plunk inbound on a subdomain (e.g. mail.yourdomain.com or support.yourdomain.com) so you can keep your main mailbox on the existing provider. The subdomain still needs to be verified for sending in Plunk before its MX will be accepted.

Wait for DNS propagation

DNS changes take anywhere from a few minutes to 48 hours to propagate. You can verify the MX record is live:

dig MX yourdomain.com

You should see the value Plunk gave you in the response.

Send a test email

Send an email from any external account to any address at your domain (e.g. anything@yourdomain.com) and check that:

  • A new Email row appears in the project's Activity feed with type Inbound.
  • The sender shows up as a contact in the Contacts tab.
  • An email.received event is recorded on that contact.

What gets stored

Every accepted inbound email shows up in your project's Activity feed alongside outbound emails — you can search, filter, and inspect them the same way.

Plunk stores: the parsed and sanitized HTML body (or plain text if no HTML is available), the headers surfaced in the event payload below, the authentication and spam verdicts, and the message ID.

Plunk does not store: the original raw message, attachments, threading headers (In-Reply-To, References), or headers beyond what's exposed on the event. If you need any of those, forward the email to your own service via a WEBHOOK step in the workflow that fires.

The email.received event

The event is emitted on the sender contact (auto-created if it doesn't exist yet) and carries the full parsed message in its data field:

Prop

Type

The event is always emitted — Plunk does not block emails based on the spam, virus, or authentication verdicts. Filter them yourself in the workflow that fires.

In templates and workflow steps, access these fields via the event variable namespace, e.g. {{event.subject}}, {{event.from}}, or {{event.body}}.

Building workflows on inbound

Auto-reply

A minimal auto-reply on support@yourdomain.com:

  1. Trigger: email.received.
  2. Condition: continue only if event.to equals support@yourdomain.com and event.spamVerdict == "PASS" and event.virusVerdict == "PASS".
  3. Send email:
    • To: {{event.from}}
    • Subject: Re: {{event.subject}}
    • Body: Thanks for your message. We've received your email and will respond within one business day.

Use a TRANSACTIONAL template for the auto-reply so it bypasses subscription checks (the sender is auto-subscribed but you don't want to fail to reply if they later unsubscribe).

Routing by recipient

Route different addresses to different downstream actions in a single workflow:

  1. Trigger: email.received.
  2. Condition: branch on event.to:
    • support@… → ticketing webhook → auto-reply.
    • sales@… → CRM webhook → notify Slack.
    • billing@… → billing system webhook.

Forward to your API

For richer processing (NLP classification, ticket creation, attachments not captured by Plunk), forward the email to your own backend:

  1. Trigger: email.received.
  2. Webhook:
    • URL: https://api.example.com/inbound
    • Method: POST
    • Body:
      {
        "from": "{{event.from}}",
        "subject": "{{event.subject}}",
        "body": "{{event.body}}",
        "messageId": "{{event.messageId}}",
        "timestamp": "{{event.timestamp}}",
        "verdicts": {
          "spam": "{{event.spamVerdict}}",
          "virus": "{{event.virusVerdict}}",
          "spf": "{{event.spfVerdict}}",
          "dkim": "{{event.dkimVerdict}}",
          "dmarc": "{{event.dmarcVerdict}}"
        }
      }

Filter spam and virus before processing

Always include a CONDITION step early in the workflow that drops anything where spamVerdict or virusVerdict is FAIL. Plunk does not pre-filter for you.

Multi-project domains

If the same domain is verified in multiple projects, every inbound email is delivered to every project that has it verified — each gets its own Email record, its own contact upsert, and its own email.received event. This is by design (it lets you split a single inbox across staging and production projects, or hand off the same inbound stream to multiple teams).

If you don't want this, only verify the domain in one project at a time.

Billing

Inbound emails count toward your project's email usage at 1 credit per received email, just like outbound. The free tier and paid tiers consume the same pool.

You can set a per-project inbound cap under Billing → Limits. Once the cap is reached, inbound emails are silently dropped for that project until the cap resets — they aren't queued or replayed. Other projects sharing the same domain are unaffected by another project's cap.

Security considerations

  • Treat the body as untrusted user input. Plunk sanitizes HTML to prevent the obvious script-injection paths, but the message can still contain phishing links, social-engineering content, and unicode lookalikes. Don't render the body verbatim in any UI you control without re-escaping it for that context.
  • Authentication verdicts are advisory. Plunk records SPF / DKIM / DMARC results on the event but does not enforce them. Build your own policy: dropping unauthenticated mail at the workflow's first CONDITION step is a sensible default for sensitive inboxes (billing, account changes).
  • Senders are auto-subscribed. Inbound senders enter your audience as subscribed contacts. If you don't want that, add an UPDATE_CONTACT step at the end of the inbound workflow to set subscribed: false.
  • Reply-loop risk. If your auto-reply sends back to a domain you also receive on (or to a list address), you can create an infinite loop. Add a CONDITION that drops messages where event.from matches your own domain.

Limitations

  • Catch-all only: Plunk routes anything sent to any address at your domain to the same handler. You filter on event.to inside the workflow.
  • No attachments: attachments are dropped during parsing. Forward to your own service if you need them.
  • No raw MIME: the original message is not retained.
  • No threading: Plunk doesn't group inbound messages into threads or correlate them with outbound replies.
  • 40 MB size cap: messages larger than 40 MB are rejected before processing.

Troubleshooting