Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

Gmail API

Connect FlutterFlow to Gmail API by extending Google sign-in with the `gmail.send` scope via a Custom Action, then building a Gmail API Group that sends email via `POST /users/me/messages/send`. The critical FlutterFlow-specific step: Gmail requires the entire email as a base64url-encoded RFC 2822 message in a single `raw` field — a Custom Function must build and encode that string before the API Call.

What you'll learn

  • How to extend FlutterFlow's Google sign-in to request the `gmail.send` OAuth scope
  • Why Gmail's send endpoint requires a base64url-encoded RFC 2822 message (not simple fields)
  • How to write a Custom Function that assembles and encodes the MIME email string
  • How to build a Gmail API Group and wire the send action to a button
  • How to read inbox messages using Gmail API's list and get endpoints
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced14 min read60 minutesCommunicationLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Gmail API by extending Google sign-in with the `gmail.send` scope via a Custom Action, then building a Gmail API Group that sends email via `POST /users/me/messages/send`. The critical FlutterFlow-specific step: Gmail requires the entire email as a base64url-encoded RFC 2822 message in a single `raw` field — a Custom Function must build and encode that string before the API Call.

Quick facts about this guide
FactValue
ToolGmail API
CategoryCommunication
MethodFlutterFlow API Call
DifficultyAdvanced
Time required60 minutes
Last updatedJuly 2026

Send Gmail from Your FlutterFlow App Using the Signed-In User's Account

Gmail API lets your FlutterFlow app send email as the signed-in user — from their own Gmail address, with their email identity. This is fundamentally different from transactional email tools like Mailgun or SendGrid, which send from a domain you own. Gmail API sends from the user's personal or Workspace address, which means the recipient sees the user's real name and address in the From field. Typical use-cases: a coaching app that lets clients send recap emails, a CRM that sends follow-up messages directly from the salesperson's Gmail, or an onboarding flow that sends a welcome email from the founder's own inbox.

The Gmail API is free with a Google account. The sending quota is 500 emails per day for standard Gmail accounts and around 2,000 per day for Google Workspace accounts (verify current limits in the Google documentation). These are per-user-per-day limits, not per-app limits, which makes Gmail suitable for lightweight sending but not bulk campaigns — for those, use Mailgun or SendGrid. The rate limit is 250 quota units per user per second, and a `messages.send` call costs 100 units, so you have headroom for occasional sends.

The integration architecture in FlutterFlow has two mandatory pieces that often trip builders: First, FlutterFlow's built-in Google Sign-In only grants basic profile scopes — you must add `gmail.send` via a Custom Action. Second, the send endpoint (`POST /users/me/messages/send`) does not accept a simple JSON body with `to`, `subject`, and `body` fields. It requires the entire email formatted as an RFC 2822 MIME message, then base64url-encoded (not standard base64 — use URL-safe encoding, no padding). A FlutterFlow Custom Function handles this encoding step before the API Call fires.

Integration method

FlutterFlow API Call

FlutterFlow accesses the Gmail API through a user's OAuth token obtained via a Custom Action that extends Google sign-in with the `gmail.send` scope. The fundamental challenge is Gmail's send endpoint: it does not accept plain to/subject/body fields — it requires a base64url-encoded RFC 2822 MIME message in a single `raw` field. A Custom Function in FlutterFlow builds the MIME string and encodes it. For reading large inboxes or sending on behalf of other users, use a Cloud Function with service account credentials instead.

Prerequisites

  • A Google account (Gmail API is free; no billing required for the API itself)
  • A Google Cloud project with the Gmail API enabled (console.cloud.google.com → APIs & Services → Enable APIs → 'Gmail API')
  • OAuth 2.0 configured: OAuth consent screen with `https://www.googleapis.com/auth/gmail.send` scope (and optionally `gmail.readonly` for reading), and an OAuth client ID
  • A FlutterFlow project with Firebase Auth configured and Google Sign-In enabled
  • A paid FlutterFlow plan for Custom Actions and Custom Functions (Standard or higher)

Step-by-step guide

1

Enable Gmail API and configure the gmail.send OAuth scope

Open console.cloud.google.com and navigate to your Firebase project. In the left menu, click APIs & Services, then Library. Search for 'Gmail API' and click Enable. Next, click OAuth consent screen. You'll need to add the Gmail scope to your app's authorized scopes. Click Add or Remove Scopes and find `https://www.googleapis.com/auth/gmail.send` — it appears as 'Send email on your behalf'. Add it. If you want to also read inbox messages, add `https://www.googleapis.com/auth/gmail.readonly` as well. The `gmail.send` scope is classified as sensitive by Google. During development you can use it with test users (added to the Test Users list on the consent screen). Before releasing to the public, you must submit your app for Google's OAuth verification — this review process can take 1-4 weeks and requires a privacy policy URL, an explanation of why you need the scope, and a demo video. For now, add your own Google account to the Test Users list and proceed with development. Click Save and Continue through the consent screen steps.

Pro tip: The OAuth consent screen App name and logo are what users see when they authorize the Gmail scope. Use your app's real name and logo so the permission prompt looks trustworthy, not like a generic test app.

Expected result: The Gmail API is enabled in your project and the `gmail.send` scope is listed in your OAuth consent screen's authorized scopes.

2

Create a Custom Action to sign in with the Gmail scope

In FlutterFlow, click Custom Code in the left navigation panel. Click + Add and choose Action. Name it 'signInWithGmailScope'. In the Dependencies field, add `google_sign_in: ^6.2.1` (verify the latest version on pub.dev). Set the Return Value to String — the action returns the OAuth access token. Paste the code below. This Custom Action signs the user in with Google, explicitly requesting the `gmail.send` scope in addition to the basic profile scopes. It returns the access token as a string. After calling this action in FlutterFlow's Action Flow Editor, store the returned value in an App State or Page State variable named `gmailAccessToken`. If the user was previously signed in without the Gmail scope, calling `signOut()` first ensures they get the full permission prompt. Custom Actions that use `google_sign_in` cannot run in FlutterFlow's web Run/Test mode — use Test on Device (Android or iOS) or download the project and run `flutter run` locally to test this step. The access token expires in approximately 1 hour — for a production app, implement token refresh logic or prompt re-sign-in when a 401 is returned from the API.

sign_in_with_gmail_scope.dart
1// Custom Action: signInWithGmailScope
2// Add dependency: google_sign_in: ^6.2.1
3// Return type: String
4
5import 'package:google_sign_in/google_sign_in.dart';
6
7Future<String> signInWithGmailScope() async {
8 final GoogleSignIn googleSignIn = GoogleSignIn(
9 scopes: [
10 'email',
11 'profile',
12 'https://www.googleapis.com/auth/gmail.send',
13 ],
14 );
15
16 try {
17 await googleSignIn.signOut(); // Force re-consent to add new scope
18 final GoogleSignInAccount? account = await googleSignIn.signIn();
19 if (account == null) return '';
20
21 final GoogleSignInAuthentication auth = await account.authentication;
22 final token = auth.accessToken ?? '';
23 return token;
24 } catch (e) {
25 return '';
26 }
27}

Pro tip: Store the signed-in user's email address (from `account.email`) as a separate App State variable if you want to show it in the UI or use it as the From address confirmation.

Expected result: The Custom Action appears in FlutterFlow's Custom Code panel. When tested on a device, it shows the Google account picker, requests Gmail permission, and returns an access token string.

3

Create a Custom Function to build the RFC 2822 MIME message

This is the step where most FlutterFlow builders get stuck: Gmail's send endpoint does not accept a JSON body with `to`, `subject`, and `body` fields. It accepts only a single field named `raw` containing the entire email formatted as an RFC 2822 MIME message and then base64url-encoded. You need a Custom Function to build this string. In FlutterFlow, click Custom Code → + Add → Function. Name it 'buildGmailRaw'. Set Arguments: `toEmail` (String), `fromEmail` (String), `subject` (String), `body` (String). Set the Return Value to String. Paste the Dart code below. This function assembles the MIME headers and body into the correct RFC 2822 format and encodes it as base64url (NOT standard base64 — the URL-safe variant replaces `+` with `-` and `/` with `_`, and strips padding `=` characters). Getting the encoding wrong — for example, using standard base64 or leaving padding — returns a 400 error from the Gmail API with message 'Invalid value for ByteString field'. The `utf8.encode` call ensures non-ASCII characters in the subject or body are handled correctly. Custom Functions (as opposed to Custom Actions) CAN run in FlutterFlow's web preview because they are pure Dart with no native platform calls.

build_gmail_raw.dart
1// Custom Function: buildGmailRaw
2// Arguments: toEmail (String), fromEmail (String), subject (String), body (String)
3// Return type: String
4// No external dependencies needed - dart:convert is always available
5
6import 'dart:convert';
7
8String buildGmailRaw(
9 String toEmail,
10 String fromEmail,
11 String subject,
12 String body,
13) {
14 // Build RFC 2822 MIME message
15 final mimeMessage = [
16 'To: $toEmail',
17 'From: $fromEmail',
18 'Subject: $subject',
19 'Content-Type: text/plain; charset=utf-8',
20 'MIME-Version: 1.0',
21 '', // blank line separates headers from body
22 body,
23 ].join('\r\n');
24
25 // Base64url encode (URL-safe, no padding)
26 final encoded = base64Url.encode(utf8.encode(mimeMessage));
27 // Strip padding '=' characters
28 return encoded.replaceAll('=', '');
29}

Pro tip: To send HTML email instead of plain text, change `Content-Type: text/plain` to `Content-Type: text/html; charset=utf-8` and put HTML markup in the body parameter.

Expected result: The buildGmailRaw Custom Function appears in FlutterFlow's Custom Code panel and can be tested immediately in the web editor by passing test values — no device needed for Custom Functions.

4

Build the Gmail API Group and sendMessage API Call

Click API Calls in the left navigation panel. Click + Add and choose Create API Group. Name it 'GmailAPI'. Set the Base URL to `https://gmail.googleapis.com/gmail/v1`. Click the Headers tab and add a shared header: key `Authorization`, value `Bearer [accessToken]`. In the Variables tab, add `accessToken` (String). Now add an API Call inside this group: click + Add API Call, name it 'SendMessage'. Set the method to POST. Set the API URL to `/users/me/messages/send`. In the Body tab, set body type to JSON. Add one field: key `raw`, value `[rawMessage]`. In the Variables tab, add `rawMessage` (String) — this will hold the output of the buildGmailRaw Custom Function. In the Response & Test tab, enter test values: a real access token from a signed-in session (copy it temporarily from a debug print), a base64url-encoded test message. Click Test API Call — a successful send returns a 200 with the sent message's ID. Add a second API Call: 'ListMessages', method GET, URL `/users/me/messages?maxResults=10`, Variable `q` (String, default `is:unread`) — add it as a query param `q=[q]`. Add a third: 'GetMessage', method GET, URL `/users/me/messages/[messageId]`, Variable `messageId` (String).

send_message_body.json
1{
2 "raw": "[rawMessage]"
3}

Pro tip: The Gmail API base URL is `https://gmail.googleapis.com/gmail/v1` (NOT `https://www.googleapis.com/gmail/v1`) — both work but the first is the canonical current URL shown in Google's documentation.

Expected result: The GmailAPI group appears in the API Calls panel with SendMessage, ListMessages, and GetMessage calls. A test send in the Response & Test tab delivers an email to the target inbox.

5

Wire the full send email action chain and test

Open the page with your email compose UI — at minimum a Subject TextField, a Body TextField (multiline), a recipient address TextField, and a Send button. Wire the Send button's Actions panel in this order: (1) Call the signInWithGmailScope Custom Action and store the result in `gmailAccessToken` Page State. If the returned value is empty, show a Snackbar 'Could not sign in with Google — please try again' and stop. (2) Call the buildGmailRaw Custom Function, passing `toEmail` from the recipient field, `fromEmail` from the signed-in user's email App State variable, `subject` from the subject field, and `body` from the body field. Store the result in a `rawMessage` Page State variable. (3) Call the SendMessage API Call, binding `accessToken` to `gmailAccessToken` and `rawMessage` to the Custom Function output. (4) On success (check for a 200 response or non-empty `$.id` JSON Path), show a Snackbar 'Email sent!', clear the form fields, and optionally navigate back. On failure, show the error. Add a loading indicator (Page State boolean `isSending`) that shows a CircularProgressIndicator during the API call and disables the Send button. Test first in the web preview for the Custom Function step (buildGmailRaw), then on a real device for the full end-to-end flow including Google sign-in. If you'd rather skip the custom code setup, RapidDev's team builds FlutterFlow integrations like this every week — free scoping call at rapidevelopers.com/contact.

Pro tip: Set the recipient email field's keyboard type to 'Email Address' in FlutterFlow's widget settings to show the @ keyboard on mobile and improve input accuracy.

Expected result: Tapping Send on a real device signs in with Google, builds the RFC 2822 message, sends it via Gmail API, and shows 'Email sent!' confirmation. The email appears in the recipient's Gmail inbox from the sender's real Gmail address.

Common use cases

CRM app that sends follow-up emails directly from the salesperson's Gmail

A FlutterFlow CRM shows a list of customer contacts. When the salesperson taps a contact and fills in a message, the app calls Gmail API to send the email from the salesperson's own Gmail address. The recipient sees the email coming from the salesperson's real email identity, not a no-reply address.

FlutterFlow Prompt

On the contact detail page, add a 'Send Email' button that opens a form with Subject and Body text fields. When sent, call the Gmail API using the signed-in user's OAuth token to send from their Gmail address to the contact's email.

Copy this prompt to try it in FlutterFlow

Client portal app that sends meeting recap emails to the user themselves

After a client completes a session in the FlutterFlow app, an action chain automatically drafts a recap email with key points and sends it to the signed-in user's own Gmail inbox. The user opted in during onboarding and the app sends on their behalf from their address.

FlutterFlow Prompt

After the session summary is saved, use the Gmail API to send an email to the signed-in user's email address with subject 'Your Session Recap' and the session notes as the email body.

Copy this prompt to try it in FlutterFlow

Inbox reader that shows unread Gmail messages in a FlutterFlow ListView

A FlutterFlow page shows the user's unread Gmail messages in a ListView, pulled via the Gmail list API with the query `is:unread`. Tapping a message loads the full content. This gives the user a focused inbox view inside the app for messages relevant to the app's context.

FlutterFlow Prompt

Add a page with a ListView that loads unread Gmail messages using the Gmail API list call with q=is:unread. Show the sender name, subject, and preview snippet. Tapping opens the full message.

Copy this prompt to try it in FlutterFlow

Troubleshooting

Gmail API returns 400 'Invalid value for ByteString field'

Cause: The `raw` field contains incorrectly encoded content — either using standard base64 (with `+` and `/`) instead of base64url (with `-` and `_`), or the padding `=` characters were not stripped.

Solution: Verify the buildGmailRaw Custom Function uses `base64Url.encode()` (not `base64.encode()`) and removes `=` padding with `.replaceAll('=', '')`. Standard base64 from Dart's `base64.encode()` will produce invalid output for Gmail's `raw` field.

Gmail API returns 403 with `insufficientPermissions`

Cause: The OAuth token was obtained without the `gmail.send` scope — either the user was signed in before the scope was added to the consent screen, or FlutterFlow's built-in Google login was used instead of the Custom Action.

Solution: Call the signInWithGmailScope Custom Action (Step 2) rather than FlutterFlow's Google Sign-In widget. If the user has an existing sign-in session without the Gmail scope, the `signOut()` call in the action forces re-consent. Verify the `gmail.send` scope is listed in your Google Cloud Console OAuth consent screen.

The API Call works in the FlutterFlow editor test but fails with 401 Unauthorized on the device after some time

Cause: The OAuth access token has expired. Google access tokens typically expire in 3600 seconds (1 hour).

Solution: Call the signInWithGmailScope Custom Action again to get a fresh token before each send operation. For a smoother UX, call `googleSignIn.signInSilently()` first (which returns a cached account without showing the picker), then get a fresh authentication token — this refreshes the token without interrupting the user.

Emails send successfully but the To name shows as the email address only, not a display name

Cause: The MIME `To` field only has the email address, not the `Name <email>` format.

Solution: Update the buildGmailRaw function to accept a `toName` argument and format the To header as `Name <email>`: `'To: $toName <$toEmail>'`. If no display name is available, the email address alone is also valid.

Best practices

  • Request only the `gmail.send` scope if you only need to send — do not request broader scopes like `gmail.modify` or `mail.google.com` unless you have a specific need for them. Google's scope review process is stricter for broader scopes.
  • Always use base64url encoding (not standard base64) with padding stripped for the `raw` field — this is the single most common error in Gmail API integrations. Dart's `base64Url.encode()` from `dart:convert` handles this correctly.
  • The Gmail API is for per-user transactional email only — do not use it for bulk sends, marketing campaigns, or sending to users who have not explicitly consented. High-volume sending gets accounts flagged. Use Mailgun or SendGrid for bulk email.
  • Store the signed-in user's email address alongside the access token so the From field is accurate and you can display it in the UI to confirm whose account is being used.
  • Handle the 401 Unauthorized error gracefully in the API Call's error branch by calling signInWithGmailScope again to silently refresh the token rather than showing a raw error to the user.
  • For the OAuth consent screen, prepare a clear explanation of why your app needs Gmail access — Google's reviewers look for a specific, legitimate use case in the app's description.
  • Validate the recipient email address format in FlutterFlow before building the MIME message — a malformed address causes a 400 error that is harder to diagnose than a client-side format check.

Alternatives

Frequently asked questions

Why does Gmail API need a base64url-encoded MIME message instead of simple to/subject/body fields?

The Gmail API models email as the raw RFC 2822 format that email protocols actually use — including all headers, MIME parts, and proper encoding. This makes it more powerful (you can send HTML, attachments, custom headers) but also more complex to build. The Custom Function in Step 3 abstracts this complexity so the rest of your FlutterFlow app just passes simple string parameters.

Can I use Gmail API to send from a shared inbox or alias, not just the primary Gmail address?

Yes, if the signed-in user has send-as aliases configured in their Gmail settings (Settings → Accounts and Import → Send mail as), you can use those addresses in the From field of the MIME message. However, Gmail will reject the send if the From address is not a verified alias or the primary address for the authenticated account.

What's the daily sending limit for Gmail API?

Standard Gmail accounts can send approximately 500 emails per day via the Gmail API. Google Workspace (paid) accounts can send approximately 2,000 per day (verify current limits in Google's documentation, as these can change). These are per-user limits — they count against the same quota as emails sent through the Gmail web interface.

Can I send attachments through Gmail API from FlutterFlow?

Yes, but it requires building a multipart MIME message instead of a plain text message. You'd need to extend the buildGmailRaw Custom Function to handle multipart/mixed content type, encode the attachment as base64, and assemble the multipart boundary format. This is doable in Dart but significantly more complex — consider it an advanced enhancement after the basic send flow works.

Is there a simpler way to send email from FlutterFlow without dealing with Gmail's encoding?

Yes — if you don't need the email to come from the user's own Gmail address, use Mailgun, SendGrid, or AWS SES via a Cloud Function proxy. These services accept simple JSON fields (to, subject, body) and handle the email formatting themselves. Gmail API is specifically for when you need the email to appear as coming from the signed-in user's Gmail account.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.