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.
| Fact | Value |
|---|---|
| Tool | ProtonMail API |
| Category | Communication |
| Method | FlutterFlow API Call |
| Difficulty | Advanced |
| Time required | 60 minutes |
| Last updated | July 2026 |
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.
Integration method
There is no public REST API for ProtonMail that accepts a Bearer token and sends email on behalf of your account. Proton's end-to-end encryption model routes third-party access through SMTP only. A Firebase Cloud Function uses nodemailer with your Proton SMTP submission token (Business plan) or Bridge credentials (paid Mail plan) to send email server-side. FlutterFlow calls the Cloud Function with the recipient, subject, and body — the function does the actual SMTP send. This page's central job is setting that expectation clearly.
Prerequisites
- 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
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.
Pro tip: If you are on a paid personal Proton plan (Mail Plus, Unlimited) rather than a Business plan, SMTP submission tokens are not available. You would need to use Bridge locally, which only works for desktop-based tools — not cloud functions. For FlutterFlow integration, Proton Business is the only viable path.
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.
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.
1// functions/index.js2const functions = require('firebase-functions');3const admin = require('firebase-admin');4const nodemailer = require('nodemailer');56admin.initializeApp();7const db = admin.firestore();89const PROTON_USER = functions.config().proton.user;10const PROTON_TOKEN = functions.config().proton.token;1112const transporter = nodemailer.createTransport({13 host: 'smtp.protonmail.com',14 port: 587,15 secure: false, // STARTTLS on port 58716 requireTLS: true,17 auth: {18 user: PROTON_USER,19 pass: PROTON_TOKEN // SMTP submission token, NOT account password20 }21});2223exports.sendViaProton = functions.https.onRequest(async (req, res) => {24 if (req.method !== 'POST') return res.status(405).send('Method Not Allowed');2526 const { to, subject, body, htmlBody } = req.body;27 if (!to || !subject || !body) {28 return res.status(400).json({ error: 'to, subject, and body are required' });29 }3031 try {32 const info = await transporter.sendMail({33 from: `"Your App" <${PROTON_USER}>`,34 to,35 subject,36 text: body,37 html: htmlBody || undefined38 });39 // Log to Firestore for records40 await db.collection('proton_sent').add({41 to,42 subject,43 messageId: info.messageId,44 sentAt: admin.firestore.FieldValue.serverTimestamp()45 });46 return res.status(200).json({ messageId: info.messageId, accepted: info.accepted });47 } catch (err) {48 return res.status(500).json({ error: err.message });49 }50});Pro tip: Set `requireTLS: true` in the transporter config. Without it, nodemailer may attempt an unencrypted connection, which Proton SMTP will reject. If you see ECONNREFUSED or ECONNRESET errors, verify port 587 is not blocked by your Firebase region's firewall rules.
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.
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.
1// FlutterFlow API Call configuration2{3 "group_name": "ProtonProxy",4 "base_url": "https://us-central1-yourproject.cloudfunctions.net",5 "calls": [6 {7 "name": "SendEmail",8 "method": "POST",9 "endpoint": "/sendViaProton",10 "headers": {},11 "body_type": "JSON",12 "body": {13 "to": "{{to}}",14 "subject": "{{subject}}",15 "body": "{{body}}",16 "htmlBody": "{{htmlBody}}"17 },18 "variables": [19 { "name": "to", "type": "String" },20 { "name": "subject", "type": "String" },21 { "name": "body", "type": "String" },22 { "name": "htmlBody", "type": "String", "default": "" }23 ]24 }25 ]26}Pro tip: Keep the `htmlBody` variable optional with an empty default. When binding from a simple text field, leave `htmlBody` empty and nodemailer will send only the plain text. Add HTML only when you have a specific template to apply.
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.
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.
Pro tip: If your form collects sensitive user data (medical info, financial details), do not log the full email body to Firestore — log only the messageId and timestamp. Proton Mail's encryption protects content in transit, but Firestore logs are plaintext.
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.
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.
1// Updated index.js with CORS support2const functions = require('firebase-functions');3const admin = require('firebase-admin');4const nodemailer = require('nodemailer');5const cors = require('cors')({ origin: true });67admin.initializeApp();8const db = admin.firestore();910const PROTON_USER = functions.config().proton.user;11const PROTON_TOKEN = functions.config().proton.token;1213const transporter = nodemailer.createTransport({14 host: 'smtp.protonmail.com',15 port: 587,16 secure: false,17 requireTLS: true,18 auth: { user: PROTON_USER, pass: PROTON_TOKEN }19});2021exports.sendViaProton = functions.https.onRequest((req, res) => {22 cors(req, res, async () => {23 if (req.method !== 'POST') return res.status(405).send('Method Not Allowed');24 const { to, subject, body, htmlBody } = req.body;25 if (!to || !subject || !body) {26 return res.status(400).json({ error: 'to, subject, and body are required' });27 }28 try {29 const info = await transporter.sendMail({30 from: `"Your App" <${PROTON_USER}>`,31 to, subject,32 text: body,33 html: htmlBody || undefined34 });35 await db.collection('proton_sent').add({36 to, subject,37 messageId: info.messageId,38 sentAt: admin.firestore.FieldValue.serverTimestamp()39 });40 return res.status(200).json({ messageId: info.messageId, accepted: info.accepted });41 } catch (err) {42 return res.status(500).json({ error: err.message });43 }44 });45});Pro tip: If you encounter XMLHttpRequest errors in FlutterFlow's web preview after adding CORS, clear your browser cache and try again — browsers sometimes cache failed preflight responses. Also verify the CORS package is listed in functions/package.json dependencies before deploying.
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.
Common 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.
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.
Copy this prompt to try it in FlutterFlow
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.
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.
Copy this prompt to try it in FlutterFlow
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.
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.
Copy this prompt to try it in FlutterFlow
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.
1const cors = require('cors')({ origin: true });2exports.sendViaProton = functions.https.onRequest((req, res) => {3 cors(req, res, async () => {4 // ... handler code ...5 });6});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.
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.
Alternatives
Mailjet has a clean REST Send API v3.1 with a free tier (6,000 emails/month) and requires only a Cloud Function for security — no SMTP, no paid plan required for basic sending.
Mailgun offers a REST API with strong deliverability, domain verification, and detailed analytics — a better fit if you need transactional email at scale without Proton's privacy model.
Gmail API supports sending via OAuth 2.0 from a Google Workspace account with a REST interface — more complex to authorize but free for most volumes and familiar to most founders.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation