BitelioBitelio

Receiving emails

Accept inbound mail on your verified domain and react to it with workflows via the email.received event

Any address at a verified domain can become an inbox: Bitelio accepts the incoming mail, stores it in your project, and fires an email.received event that workflows can react to. That's the foundation for auto-replies, ticketing, conditional forwarding — any "when an email arrives, do X" automation.

What happens when an email arrives

For each message sent to an address at your verified domain, Bitelio:

  1. Parses it and saves it as an inbound Email record, visible in the project's Activity feed.
  2. Upserts the sender as a contact — created (subscribed by default) if new, updated otherwise.
  3. Records an email.received event on that contact, available as a trigger for any workflow.

Before storage, the HTML body goes through sanitization: scripts, iframes, event handlers, and javascript: URIs are all removed. If the message has no HTML part, the plain text is kept unchanged.

Setup

Verify your domain for sending

A domain only accepts inbound mail once it's fully verified for sending (DKIM + SPF + the bounce-feedback MX). Work through the Verifying domains guide before continuing.

Add the inbound MX record

In your project's Domains tab, expand the verified domain and find the Inbound Email section. It displays the exact MX record value for your DNS — copy it and create the 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 Bitelio. The usual fix is to receive Bitelio 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 Bitelio before its MX will be accepted.

Wait for DNS propagation

Propagation can take anywhere from minutes to 48 hours. To confirm the MX record has gone live:

dig MX yourdomain.com

The value from the dashboard should appear in the output.

Send a test email

From any external account, email an arbitrary address at your domain (e.g. anything@yourdomain.com) and verify that:

  • The project's Activity feed shows a new Email row with type Inbound.
  • The sender appears in the Contacts tab.
  • That contact has an email.received event recorded.

What gets stored

Accepted inbound mail lands in the project's Activity feed right next to your outbound emails, with the same search, filter, and inspection tools.

What Bitelio keeps: the parsed, sanitized HTML body (falling back to plain text when there's no HTML), the headers listed in the event payload below, the authentication and spam verdicts, and the message ID.

What it doesn't keep: the raw original message, attachments, threading headers (In-Reply-To, References), or any headers beyond those on the event. When you need those, have the triggered workflow forward the email to your own service through a WEBHOOK step.

The email.received event

The event lands on the sender's contact record (created on the fly if needed), with the fully parsed message in its data field:

Prop

Type

Emission is unconditional — spam, virus, and authentication verdicts never cause Bitelio to withhold the event. Any filtering is yours to do inside the workflow it triggers.

Within templates and workflow steps, these fields live under the event variable namespace: {{event.subject}}, {{event.from}}, {{event.body}}, and so on.

Building workflows on inbound

Auto-reply

The simplest possible auto-reply for 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.

Give the auto-reply a TRANSACTIONAL template so subscription checks don't apply — senders are auto-subscribed, but if one later unsubscribes, your reply should still go out.

Routing by recipient

One workflow can fan different addresses out to different actions:

  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

When processing needs more than Bitelio offers (NLP classification, ticket creation, attachments Bitelio doesn't capture), hand the email off 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

Make a habit of placing a CONDITION step near the top of every inbound workflow that discards messages whose spamVerdict or virusVerdict is FAIL — Bitelio does no pre-filtering on your behalf.

Multi-project domains

When several projects have verified the same domain, each incoming email fans out to all of them — every project gets its own Email record, contact upsert, and email.received event. That's intentional: it lets staging and production share one inbox, or several teams consume the same inbound stream.

To avoid the fan-out, keep the domain verified in a single project at a time.

Billing

Each received email consumes 1 credit from your project's email usage, exactly like an outbound send. Free and paid tiers draw from the same pool.

Under Billing → Limits you can put a per-project cap on inbound. When the cap is hit, that project's inbound emails are silently dropped until the cap resets — nothing is queued or replayed later. A cap on one project has no effect on other projects verified on the same domain.

Security considerations

  • Treat the body as untrusted user input. Sanitization closes the obvious script-injection paths, but phishing links, social-engineering copy, and unicode lookalikes all survive it. Never render the body verbatim in your own UI without escaping it appropriately for that context.
  • Authentication verdicts are advisory. SPF / DKIM / DMARC results are recorded on the event, not enforced. The policy is yours to define — for sensitive inboxes (billing, account changes), rejecting unauthenticated mail in the workflow's first CONDITION step is a reasonable baseline.
  • Senders are auto-subscribed. Every inbound sender joins your audience as a subscribed contact. To opt them out, finish the inbound workflow with an UPDATE_CONTACT step setting subscribed: false.
  • Reply-loop risk. An auto-reply aimed at a domain you also receive on (or at a list address) can loop forever. Guard against it with a CONDITION that discards messages whose event.from is on your own domain.

Limitations

  • Catch-all only: every address at the domain funnels into the same handler; distinguishing recipients happens inside the workflow via event.to.
  • No attachments: parsing discards them. Forward to your own service if you need them.
  • No raw MIME: the original message isn't kept.
  • No threading: inbound messages aren't grouped into threads or matched to outbound replies.
  • 40 MB size cap: anything above 40 MB is rejected before processing begins.

Troubleshooting