# ProtonMail API

- Tool: FlutterFlow
- Difficulty: Advanced
- Time required: 60 minutes
- Last updated: July 2026

## TL;DR

There is no public ProtonMail REST API for third-party email sending. Proton's end-to-end encryption means mail access is available only through SMTP — either via Proton Mail Bridge (paid personal plans, desktop-only) or SMTP submission tokens (Proton Business). To send ProtonMail-originated email from FlutterFlow, deploy a Firebase Cloud Function using Node.js and nodemailer with your SMTP token. FlutterFlow then calls that function via a single API Call. A paid Proton plan is required.

## Why ProtonMail Has No Public REST API — and What to Do Instead

If you've searched for a 'ProtonMail API key' or 'ProtonMail REST endpoint,' you've likely hit a wall. Unlike Mailjet, Mailgun, or SendGrid — which expose a `POST /send` REST endpoint with an API key — Proton Mail deliberately does not offer a public REST API for sending email on behalf of an account. This is an architectural choice, not an omission: Proton Mail's core value is end-to-end encryption, and a REST API would require Proton's servers to handle plaintext message content, which breaks the encryption model.

Instead, Proton provides two SMTP-based access paths for third-party sending. The first is Proton Mail Bridge — a desktop application (macOS/Windows/Linux) that runs locally and exposes an SMTP server on 127.0.0.1 for email clients like Thunderbird or Apple Mail. Bridge works well for local tools but cannot be used as a cloud sender: it must run on the user's desktop machine, making it unsuitable for a Firebase Cloud Function or any hosted environment.

The second path — and the one that works for FlutterFlow apps — is SMTP submission tokens, available on Proton Business and Proton for Business plans. These tokens let you send email through Proton's SMTP server (`smtp.protonmail.com`, port 587 with STARTTLS) from a hosted environment. Combined with nodemailer in a Firebase Cloud Function, this enables your FlutterFlow app to send from your Proton Mail address. The SMTP token is a hard secret and belongs only in the Cloud Function's environment configuration — never in FlutterFlow or any client-side code.

## Before you start

- A Proton Business or Proton for Business account — free/personal Proton plans do not offer SMTP submission tokens
- A verified custom domain in Proton Mail to use as the sender address (SMTP tokens are tied to domain addresses)
- A Firebase project on the Blaze (pay-as-you-go) plan with Cloud Functions enabled — required for outbound SMTP connections
- A FlutterFlow project with at least one form screen and a Firestore database (optional but recommended for logging sent emails)
- Basic Node.js familiarity to install nodemailer in the functions directory and deploy the Cloud Function

## Step-by-step guide

### 1. Understand the ProtonMail sending options and choose SMTP submission tokens

Before writing any code, confirm you're using the right Proton access path. There are two options:

**Proton Mail Bridge** runs as a local desktop application and exposes SMTP on `127.0.0.1:1025` (or similar). It works for local tools — Thunderbird, Apple Mail, or a local Node.js script — but cannot be used in a Firebase Cloud Function because a cloud server has no access to your local machine. Do not attempt to use Bridge as a cloud sender.

**SMTP submission tokens** (Proton Business) are the hosted solution. They allow an external SMTP client — in your case, a Firebase Cloud Function — to authenticate with Proton's public SMTP server (`smtp.protonmail.com`, port 587, STARTTLS) using a token tied to a specific email address on your domain.

To generate an SMTP submission token: log in to your Proton Business account at account.proton.me, go to **Settings** → **Email** (or **All Settings** → **Mail** → **SMTP submission**). Click **Generate Token** for the address you want to use as the sender (e.g., `noreply@yourdomain.com`). Copy the token immediately — it is shown only once. Store it in a password manager.

Also confirm that your sender domain is verified in Proton Mail (SPF, DKIM, DMARC records should all be green in **Settings** → **Domain names**). Sending from an unverified domain results in delivery failures or spam classification.

**Expected result:** You have a Proton Business SMTP token copied and stored securely, and your sender domain's DNS records are verified in the Proton Mail dashboard.

### 2. Deploy a Firebase Cloud Function that sends email via Proton SMTP

In the Firebase Console, navigate to your project and confirm it is on the Blaze (pay-as-you-go) plan — the free Spark plan blocks all outbound network connections, including SMTP. Open Functions and prepare your local `functions/` directory.

Install nodemailer as a dependency in your `functions/package.json` by adding `"nodemailer": "^6.9.0"` to the dependencies object (Firebase CLI will install it during deploy). Create the Cloud Function shown below.

Store your Proton SMTP credentials as Firebase environment config (run these in your terminal before deploying):
```
firebase functions:config:set proton.user="noreply@yourdomain.com" proton.token="YOUR_SMTP_SUBMISSION_TOKEN"
```
The function creates a nodemailer transporter connecting to `smtp.protonmail.com` on port 587 with STARTTLS (`requireTLS: true` or `starttls: {required: true}`). The username is your full Proton email address. The password is the SMTP submission token (NOT your Proton account password).

After deploying with `firebase deploy --only functions`, the HTTPS trigger URL will appear in the Firebase Console. This URL is what FlutterFlow will POST to. Test the function first using a browser-based REST client (Hoppscotch, Postman) by sending a POST with a JSON body containing `to`, `subject`, and `body` fields before wiring it into FlutterFlow.

```
// functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');

admin.initializeApp();
const db = admin.firestore();

const PROTON_USER = functions.config().proton.user;
const PROTON_TOKEN = functions.config().proton.token;

const transporter = nodemailer.createTransport({
  host: 'smtp.protonmail.com',
  port: 587,
  secure: false, // STARTTLS on port 587
  requireTLS: true,
  auth: {
    user: PROTON_USER,
    pass: PROTON_TOKEN   // SMTP submission token, NOT account password
  }
});

exports.sendViaProton = functions.https.onRequest(async (req, res) => {
  if (req.method !== 'POST') return res.status(405).send('Method Not Allowed');

  const { to, subject, body, htmlBody } = req.body;
  if (!to || !subject || !body) {
    return res.status(400).json({ error: 'to, subject, and body are required' });
  }

  try {
    const info = await transporter.sendMail({
      from: `"Your App" <${PROTON_USER}>`,
      to,
      subject,
      text: body,
      html: htmlBody || undefined
    });
    // Log to Firestore for records
    await db.collection('proton_sent').add({
      to,
      subject,
      messageId: info.messageId,
      sentAt: admin.firestore.FieldValue.serverTimestamp()
    });
    return res.status(200).json({ messageId: info.messageId, accepted: info.accepted });
  } catch (err) {
    return res.status(500).json({ error: err.message });
  }
});
```

**Expected result:** The Cloud Function deploys successfully and a Postman/Hoppscotch test POST returns `{"messageId":"...","accepted":["recipient@example.com"]}` — and the email arrives in the recipient's inbox.

### 3. Create the API Call in FlutterFlow pointing at your Cloud Function

With the Cloud Function deployed and tested, configure FlutterFlow to call it. In the FlutterFlow editor, click **API Calls** in the left navigation panel. Click **+ Add** → **Create API Group**. Name the group `ProtonProxy` and set the Base URL to the root of your Firebase Functions URL (e.g., `https://us-central1-yourproject.cloudfunctions.net`).

Inside the group, click **+ Add API Call** and name it `SendEmail`. Set the method to `POST` and the endpoint to `/sendViaProton` (matching your function export name exactly). In the **Variables** tab, add four variables:
- `to` (String) — the recipient's email address
- `subject` (String) — the email subject line
- `body` (String) — the plain-text email body
- `htmlBody` (String, optional) — HTML version of the body

In the **Body** section, choose **JSON** and enter:
```json
{"to":"{{to}}","subject":"{{subject}}","body":"{{body}}","htmlBody":"{{htmlBody}}"}
```

No `Authorization` header is needed here — authentication is fully handled inside the Cloud Function. Switch to **Response & Test**, paste a sample success response `{"messageId":"<abc123@protonmail.com>","accepted":["recipient@example.com"]}`, and click **Generate JSON Paths** to create `$.messageId` and `$.accepted` paths.

Click **Test API Call** with real values (use your own email as `to` for testing) and confirm you receive the email. If you see a 500 error, check the Firebase Function logs in the Console for SMTP error details.

```
// FlutterFlow API Call configuration
{
  "group_name": "ProtonProxy",
  "base_url": "https://us-central1-yourproject.cloudfunctions.net",
  "calls": [
    {
      "name": "SendEmail",
      "method": "POST",
      "endpoint": "/sendViaProton",
      "headers": {},
      "body_type": "JSON",
      "body": {
        "to": "{{to}}",
        "subject": "{{subject}}",
        "body": "{{body}}",
        "htmlBody": "{{htmlBody}}"
      },
      "variables": [
        { "name": "to", "type": "String" },
        { "name": "subject", "type": "String" },
        { "name": "body", "type": "String" },
        { "name": "htmlBody", "type": "String", "default": "" }
      ]
    }
  ]
}
```

**Expected result:** The FlutterFlow API Call test returns a 200 response with a messageId, and the test email arrives in the recipient's inbox from your Proton Mail address.

### 4. Build the email form screen and wire the send action

Add a new screen in FlutterFlow for your email composition form, or integrate the send action into an existing screen (a contact form, a support request screen, or an order confirmation). Place the following widgets:
- A `TextField` for the recipient's email address (or pre-fill it from a Firestore user document)
- A `TextField` for the subject line
- A multi-line `TextField` for the message body
- A `Button` labeled 'Send Email'

Select the Button, open its **Actions** panel, and click **+ Add Action**. Choose **Backend/API Call** → **ProtonProxy → SendEmail**. In the variable bindings:
- `to`: bind to the recipient TextField's current value (or a hardcoded address for a contact-to-owner scenario)
- `subject`: bind to the subject TextField's current value
- `body`: bind to the message body TextField's current value
- `htmlBody`: leave empty (or bind to a pre-composed HTML string stored in App State)

Add a second chained action: **Show Snack Bar** with conditional text — if the response contains `$.messageId` (non-empty string), show 'Email sent successfully from your Proton Mail address.' Otherwise, show 'Failed to send — please try again or check your connection.'

For contact-form use cases where the app owner is always the recipient, hard-code the `to` field (e.g., `support@yourdomain.com`) in the API Call binding rather than showing it as a user-editable field. This prevents users from redirecting emails to unintended addresses.

**Expected result:** Tapping Send triggers the Cloud Function, the email is sent from the Proton Mail address, and the user sees a 'sent' confirmation snack bar — all within a few seconds.

### 5. Add CORS support and test on a real device

If you're using FlutterFlow's web preview, Run mode, or plan to publish as a web app, the Firebase Cloud Function must return CORS headers. Browser-based HTTP requests (from FlutterFlow's web renderer and from the published web app) include an `Origin` header, and the browser blocks responses that don't include `Access-Control-Allow-Origin`. Native iOS and Android builds don't enforce CORS, so this step is only critical for web targets.

Install the `cors` npm package in your `functions/package.json` (`"cors": "^2.8.5"`) and update your Cloud Function to use it, as shown in the code snippet below. The `cors({ origin: true })` middleware reflects the request's Origin header back in the response, which satisfies the browser's CORS check without hard-coding specific domains.

After redeploying with CORS support, test the full flow in FlutterFlow's Run mode (web preview). Open the email form screen, fill in all fields, and tap Send. If you see a successful Snack Bar and an email in the Proton inbox, the integration is complete. If you're building iOS/Android only and have confirmed the function works in the FlutterFlow API test tab, you can skip the CORS step.

For production, consider adding input validation to your Cloud Function: check that `to` is a valid email format, that `subject` is not empty, and that `body` does not exceed a reasonable length (e.g., 10,000 characters) to prevent abuse. Adding rate-limiting (checking Firestore for recent sends from the same user) is also recommended for contact forms to prevent spam.

```
// Updated index.js with CORS support
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const nodemailer = require('nodemailer');
const cors = require('cors')({ origin: true });

admin.initializeApp();
const db = admin.firestore();

const PROTON_USER = functions.config().proton.user;
const PROTON_TOKEN = functions.config().proton.token;

const transporter = nodemailer.createTransport({
  host: 'smtp.protonmail.com',
  port: 587,
  secure: false,
  requireTLS: true,
  auth: { user: PROTON_USER, pass: PROTON_TOKEN }
});

exports.sendViaProton = functions.https.onRequest((req, res) => {
  cors(req, res, async () => {
    if (req.method !== 'POST') return res.status(405).send('Method Not Allowed');
    const { to, subject, body, htmlBody } = req.body;
    if (!to || !subject || !body) {
      return res.status(400).json({ error: 'to, subject, and body are required' });
    }
    try {
      const info = await transporter.sendMail({
        from: `"Your App" <${PROTON_USER}>`,
        to, subject,
        text: body,
        html: htmlBody || undefined
      });
      await db.collection('proton_sent').add({
        to, subject,
        messageId: info.messageId,
        sentAt: admin.firestore.FieldValue.serverTimestamp()
      });
      return res.status(200).json({ messageId: info.messageId, accepted: info.accepted });
    } catch (err) {
      return res.status(500).json({ error: err.message });
    }
  });
});
```

**Expected result:** The email form works in FlutterFlow's web Run mode without CORS errors, and all sent emails appear in the `proton_sent` Firestore collection for audit tracking.

## Best practices

- Understand up front that there is no public ProtonMail REST API — the integration is SMTP-only via a Cloud Function. Setting this expectation saves hours of searching for a non-existent endpoint.
- Never place Proton SMTP credentials (username or token) in a FlutterFlow API Call header or any client-side code — they compile into the app binary. Store them exclusively in Firebase Function environment config.
- Use Proton Business SMTP submission tokens for hosted sending — Bridge is desktop-only and cannot serve a Cloud Function. Do not attempt to use Bridge credentials in a server environment.
- Verify your sender domain's SPF, DKIM, and DMARC records before going live. Unverified domains cause delivery failures and spam classification regardless of the SMTP connection quality.
- Log only the messageId and timestamp to Firestore — not the email subject or body — when sending sensitive content. Proton encrypts content in transit, but Firestore stores plaintext.
- Add basic input validation in the Cloud Function: check that `to` looks like an email address, `subject` is non-empty, and `body` does not exceed your size limit. This prevents accidental or malicious misuse of the send endpoint.
- For contact form use cases where the recipient is always the app owner, hard-code the `to` field in the Cloud Function rather than accepting it from the FlutterFlow payload. This prevents users from redirecting the function to arbitrary email addresses.
- If you expect low email volume and the privacy benefit of Proton is not critical for your use case, consider using Mailjet or SendGrid instead — both offer free REST API tiers that are significantly simpler to integrate and do not require a paid plan.

## Use cases

### Privacy-first contact form for a professional services app

A legal or healthcare app built in FlutterFlow lets clients submit inquiries through a contact form. The form data is sent via a Cloud Function to the firm's Proton Mail inbox, preserving the firm's existing privacy-first email workflow. Clients receive a confirmation, and the firm's responses stay within the Proton encrypted ecosystem.

Prompt example:

```
Build a contact form screen with Name, Email, and Message fields. When submitted, send the form data to the firm's Proton Mail address via the SMTP Cloud Function and show a 'Your message was received' confirmation to the user.
```

### Encrypted transactional notifications for a fintech app

A personal finance or crypto portfolio app needs to send account alerts (large transaction detected, login from new device) from a Proton Mail address — so the notifications themselves are end-to-end encrypted when delivered to other Proton Mail users. The Cloud Function sends via Proton SMTP on each triggered event.

Prompt example:

```
When a transaction exceeds a user-defined threshold, send an alert email from the app's Proton Mail address to the user's registered email, including the transaction amount, time, and a link to the app.
```

### Automated onboarding emails from a privacy-conscious SaaS

A B2B SaaS app built in FlutterFlow sends welcome, onboarding, and weekly digest emails through the company's Proton Mail business account — appealing to enterprise clients who specifically require vendor communication through encrypted channels. The Cloud Function queues and sends each email type based on the user's lifecycle stage in Firestore.

Prompt example:

```
Trigger a welcome email via Proton SMTP when a new user's Firestore document is created, including their name, login link, and getting-started guide URL.
```

## Troubleshooting

### Cloud Function returns 500 with error: 'Invalid login: 535 Authentication credentials invalid'

Cause: The SMTP username or password (token) in the Cloud Function config is incorrect. The username must be the full Proton Mail address, and the password must be the SMTP submission token — not the Proton account login password.

Solution: In the Firebase Console, go to Functions → Configuration and verify `proton.user` is the full email address (e.g., `noreply@yourdomain.com`) and `proton.token` is the SMTP token generated in Proton Business settings. If you're uncertain, regenerate the token in Proton Mail → Settings → SMTP submission, update the Firebase config (`firebase functions:config:set proton.token="NEW_TOKEN"`), and redeploy.

### Error: 'SMTP submission tokens are not available on your plan'

Cause: SMTP submission tokens require a Proton Business or Proton for Business plan. Free, Mail Plus, and Unlimited personal plans do not include this feature.

Solution: Upgrade to a Proton Business plan at proton.me/business to access SMTP submission tokens. As an alternative for personal plans, consider using a different email provider for automated sending — Mailjet, Mailgun, or SendGrid all offer REST APIs that are easier to integrate with FlutterFlow and do not require a paid plan for low-volume sends.

### XMLHttpRequest error in FlutterFlow's web preview when calling the Cloud Function

Cause: The Firebase Cloud Function is not returning CORS headers. Browser-based requests (FlutterFlow web preview, Run mode, published web app) require the server to include `Access-Control-Allow-Origin` in the response.

Solution: Add the `cors` npm package to `functions/package.json` and wrap the function handler with `cors({ origin: true })` middleware as shown in Step 5. Redeploy the function. Note: CORS is only required for web builds — native iOS and Android builds do not enforce CORS.

```
const cors = require('cors')({ origin: true });
exports.sendViaProton = functions.https.onRequest((req, res) => {
  cors(req, res, async () => {
    // ... handler code ...
  });
});
```

### Emails land in recipient's spam or are rejected by the receiving server

Cause: The sender domain's SPF, DKIM, or DMARC records are missing or misconfigured in DNS. Emails sent via SMTP without proper authentication records are frequently spam-filtered.

Solution: In the Proton Mail business dashboard, go to Settings → Domain names and verify that SPF, DKIM, and DMARC indicators are all green for your domain. If any are red, follow Proton's DNS configuration guide to add the required records to your domain registrar. After updating DNS records, allow up to 24-48 hours for propagation before re-testing.

## Frequently asked questions

### Is there a ProtonMail REST API with an API key I can use to send emails?

No. Proton Mail does not offer a public REST API for third-party email sending. Its end-to-end encryption architecture means all mail access happens through SMTP (Proton Mail Bridge for desktop tools, or SMTP submission tokens for Business accounts). Anyone who tells you there's a Bearer-token REST endpoint for ProtonMail sending is mistaken — you will not find one.

### Can I use Proton Mail Bridge to send from a Firebase Cloud Function?

No. Proton Mail Bridge is a desktop application that creates a local SMTP server on your machine (127.0.0.1). A Firebase Cloud Function runs in Google's cloud infrastructure and has no access to your local machine, so it cannot reach Bridge. For hosted sending, you must use SMTP submission tokens, which are available on Proton Business plans.

### Do I need to generate a new SMTP token if I change my Proton account password?

Changing your Proton account login password does not invalidate SMTP submission tokens — they are independent credentials. However, if you revoke a token manually in Proton Mail settings, or if Proton detects suspicious activity and resets tokens, you'll need to generate a new one and update your Firebase Function config. Monitor your Cloud Function logs for 535 authentication errors as an early signal.

### Will recipients see that the email came from Proton Mail?

Recipients see your sender address (e.g., `noreply@yourdomain.com`) in the From field — not 'Proton Mail'. If your domain is verified and DNS records are configured correctly, the email looks like any normal email from your domain. Proton Mail branding only appears if you use a `@proton.me` or `@protonmail.com` address rather than a custom domain.

### I'm finding this setup complex — is there a simpler way to get help?

Yes. The SMTP-through-Cloud-Function setup is genuinely more involved than a REST API integration. RapidDev's team builds FlutterFlow integrations like this every week and can handle the Cloud Function setup, SMTP configuration, and FlutterFlow wiring for you. Book a free scoping call at rapidevelopers.com/contact.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/protonmail-api
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/protonmail-api
