Docs / Bubble

Send email from Bubble with the API Connector

Bubble has no SMTP setting. Its only email option, under Settings → Domain / email, is a SendGrid API key — leave it empty and messages go out from a bubbleapps.io no-reply address. To send from your own domain through Postwing, call the REST API with Bubble's API Connector plugin: one POST call, set up once and used as an action in any workflow.

✓
You can get them on the token management page. For security reasons, a token is shown only once — at the moment it is created.

The request

Bubble does not speak SMTP, so the connection settings you would use elsewhere do not apply here. The API authenticates with the same domain token — its login and password travel in the auth object of the request body:

POST https://api.postwing.app/external/send_email_simple/

The sender must be on your verified domain and written with a display name, Acme <noreply@your-domain.com>. An address on any other domain is refused: its DKIM and SPF are not yours, so it would fail DMARC at the recipient anyway. The full field list is in the API reference.

Install the API Connector

Open Plugins → Add plugins, find API Connector (published by Bubble) and install it. Then, in the plugin's settings, add an API — call it Mail — with Authentication set to None or self-handled. The credentials go in the body, so Bubble has nothing to handle.

Create the call

Add a call to that API and fill it in:

FieldValue
NameSend email
Use asAction — so it can run from a workflow
Data typeJSON
Method and URLPOST https://api.postwing.app/external/send_email_simple/
HeaderContent-Type: application/json
Body typeJSON

Paste the body below. Every name in angle brackets becomes a parameter that Bubble lists under the body:

Body
{
  "to": [<to>],
  "subject": <subject>,
  "body": <html>,
  "sender": "Acme <noreply@your-domain.com>",
  "auth": {
    "username": "<token_login>",
    "password": "<token_password>"
  }
}
ParameterPrivateValue
token_loginCheckedThe login of an SMTP token for your domain
token_passwordCheckedThat token's password
toUncheckedTest value "you@your-domain.com", with the quotes
subjectUnchecked"Test from Bubble"
htmlUnchecked"<p>It works.</p>"
⚠
to, subject and html are filled from workflows with :formatted as JSON-safe, which escapes quotes and line breaks and wraps the value in quotes itself. That is why the body leaves them bare and the test values carry their own quotes. The token parameters are fixed strings, so they stay quoted in the body.

Tick Include errors in response and allow workflow actions to continue if you want to react to a refused send in the workflow instead of having it stop.

Initialize the call

Click Initialize call. Bubble sends a real request — the test message arrives at the address you gave — and shows the response it will expose to workflows:

json
{
  "ok": true,
  "emails": [
    {
      "ok": true,
      "message_id": "<uuid@your-domain.com>",
      "uuid": "uuid",
      "recipient": "customer@example.com",
      "kind": "to",
      "replayed": false
    }
  ]
}

If the response is an error instead, Bubble shows its text; the table below says what each one means.

Send from a workflow

In any workflow, add the action named after the API and the call — Mail - Send email — and fill its three inputs with dynamic expressions, each ending in :formatted as JSON-safe: for example Current User's email, a subject built from the order number, and the HTML of the message.

API Connector actions run on Bubble's server, and private parameters never leave it. What a page workflow can decide is the recipient, so do not wire the action to a form where a visitor types any address: that turns your domain into a way to mail strangers, and the complaints land on your reputation. Send to the current user, to your own team, or from backend workflows.

Troubleshooting

ErrorCause and fix
Invalid username or password Wrong token login or password in the private parameters, or the token was deleted.
Invalid sender, must end with @your-domain.comThe sender is not on the token's domain.
Required format: full name <username@domain.ltd>Add a display name before the address in sender.
400 with a JSON parse error A dynamic value is not formatted as JSON-safe, or the body quotes a parameter that JSON-safe already quotes.
Either `recipient` or `to` is requiredThe recipient expression evaluated to nothing.
A parameter cannot be set in the workflowIt is marked Private. Only the token parameters should be.
Hourly limit exceededYour plan's volume. It clears on its own; retry later.

Frequently asked questions

Can Bubble send email over a custom SMTP server?

No. Settings → Domain / email takes a SendGrid API key and nothing else; without one, Bubble sends from a bubbleapps.io no-reply address. To send from your own domain through another provider, call its HTTP API with the API Connector, which is what this guide does.

Can the built-in 'Send email' action go through this API?

No — that action always uses Bubble's own sending, or SendGrid if you added a key. Replace it in your workflows with the API Connector action set up here; the inputs are the same three things: recipient, subject and body.

Is the token password safe in the API Connector?

Yes, as long as its parameter is marked Private. Private values stay on Bubble's server and never reach the visitor's browser. A parameter that is not private can be set from a workflow, which is exactly why the token must never be one of them.

Why does the call fail when the message contains quotes or line breaks?

The value was inserted into the JSON raw. End every dynamic expression with :formatted as JSON-safe, and do not put quotes around the <parameter> in the body — the operator adds them itself, and quoting twice produces invalid JSON too.

How do I send to several recipients at once?

Change the body to "to": <to> (without the brackets) and pass a list of texts formatted as JSON-safe, which Bubble writes as a JSON array. Every address becomes its own message with its own delivery status.

Can I use a saved template instead of building the HTML in Bubble?

Yes. Create a second call to /external/send_email_tpl/ with recipient, tpl (the template slug), lang and a params object, and keep the HTML in the dashboard rather than in your Bubble editor.

Why did I receive an email when I clicked Initialize call?

Initialization sends a real request so Bubble can read the response. Use your own address as the test value for the recipient.

Next steps