Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Create Custom Email Templates for Notifications in FlutterFlow

FlutterFlow has no email template builder — send branded notification emails by creating a Firebase Cloud Function that generates HTML with inline CSS (never external stylesheets) and sends via SendGrid or Mailgun. Store template strings in Firestore for admin editing without redeployment. Use HTML table-based layouts for maximum email client compatibility — never use div-based layouts or CSS classes in email HTML.

What you'll learn

  • Why email HTML requires inline CSS and table layouts instead of standard web CSS
  • How to create a reusable HTML email template in a Firebase Cloud Function
  • How to store and edit email templates in Firestore for admin customization
  • How to trigger branded notification emails from FlutterFlow via Cloud Functions
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read30-45 minFlutterFlow Free+ (Cloud Functions require Firebase Blaze plan)March 2026RapidDev Engineering Team
TL;DR

FlutterFlow has no email template builder — send branded notification emails by creating a Firebase Cloud Function that generates HTML with inline CSS (never external stylesheets) and sends via SendGrid or Mailgun. Store template strings in Firestore for admin editing without redeployment. Use HTML table-based layouts for maximum email client compatibility — never use div-based layouts or CSS classes in email HTML.

Branded Email Notifications from FlutterFlow Apps

FlutterFlow's built-in Firebase push notifications are great for in-app alerts, but email notifications build trust and re-engage users who have churned. The challenge is email rendering: Gmail, Outlook, Apple Mail, and Yahoo all render HTML differently — and most of them strip CSS classes and external stylesheets entirely. This tutorial builds a Cloud Function email system using battle-tested email HTML patterns (table layouts, inline CSS), stores templates in Firestore for admin editing, and triggers sends from your FlutterFlow app.

Prerequisites

  • Firebase project with Cloud Functions enabled (Blaze plan)
  • SendGrid account with a verified sender email or domain
  • FlutterFlow project with Firestore configured
  • Basic understanding of HTML structure

Step-by-step guide

1

Understand email HTML rules before writing any code

Email HTML is not web HTML. The three most important rules: first, use inline CSS only — write style='color: #333; font-size: 16px;' on every element, never use a stylesheet or CSS classes because most email clients strip them. Second, use HTML tables for layout — div-based layouts break in Outlook, which still uses Microsoft Word as its rendering engine and does not support flexbox or grid. Third, use a maximum width of 600px and always include a plain-text alternative. Images must be hosted on a public URL (use Firebase Storage), not attached. Understanding these constraints before writing code will save you hours of debugging why your emails look broken in certain clients.

Expected result: You understand the three core email HTML constraints: inline CSS only, table-based layout, and 600px max width.

2

Create a reusable email HTML generator function

In your Firebase Cloud Functions project, create a utility file named emailTemplates.js. This file exports functions that generate complete email HTML strings. Each template accepts an options object with dynamic values like recipientName, subject, and body content. The outer shell of every email should be identical: a full HTML document with a 100%-width outer table, a 600px-wide inner table, a branded header with your logo, a content area, and a footer with unsubscribe and company address. Build this shell once as a getEmailShell() function and wrap each template in it.

functions/emailTemplates.js
1// functions/emailTemplates.js
2function getEmailShell(content, brandColor = '#4F46E5') {
3 return `
4<!DOCTYPE html>
5<html lang="en">
6<head>
7 <meta charset="UTF-8">
8 <meta name="viewport" content="width=device-width, initial-scale=1.0">
9 <title>Email</title>
10</head>
11<body style="margin:0;padding:0;background-color:#f4f4f5;font-family:Arial,sans-serif;">
12 <table width="100%" cellpadding="0" cellspacing="0" border="0"
13 style="background-color:#f4f4f5;">
14 <tr><td align="center" style="padding:24px 16px;">
15 <table width="600" cellpadding="0" cellspacing="0" border="0"
16 style="max-width:600px;width:100%;background:#ffffff;
17 border-radius:8px;overflow:hidden;
18 box-shadow:0 2px 8px rgba(0,0,0,0.08);">
19 <!-- Header -->
20 <tr><td style="background-color:${brandColor};padding:24px 32px;">
21 <h1 style="margin:0;color:#ffffff;font-size:22px;
22 font-weight:700;letter-spacing:-0.3px;">YourApp</h1>
23 </td></tr>
24 <!-- Body -->
25 <tr><td style="padding:32px;color:#374151;font-size:16px;
26 line-height:1.6;">
27 ${content}
28 </td></tr>
29 <!-- Footer -->
30 <tr><td style="background:#f9fafb;padding:20px 32px;
31 border-top:1px solid #e5e7eb;">
32 <p style="margin:0;font-size:12px;color:#9ca3af;">
33 You are receiving this email because you have an account with YourApp.
34 <a href="{{{unsubscribe_url}}}" style="color:#6b7280;">Unsubscribe</a>
35 </p>
36 </td></tr>
37 </table>
38 </td></tr>
39 </table>
40</body>
41</html>`;
42}
43
44function welcomeEmail({ recipientName }) {
45 const content = `
46 <p style="margin:0 0 16px;">Hi ${recipientName},</p>
47 <p style="margin:0 0 16px;">Welcome to YourApp! Your account is ready.</p>
48 <table cellpadding="0" cellspacing="0" border="0" style="margin:24px 0;">
49 <tr><td style="background:#4F46E5;border-radius:6px;">
50 <a href="https://yourapp.com" style="display:block;padding:12px 24px;
51 color:#ffffff;text-decoration:none;font-size:15px;font-weight:600;">
52 Get Started
53 </a>
54 </td></tr>
55 </table>
56 <p style="margin:0;font-size:14px;color:#6b7280;">
57 Questions? Reply to this email anytime.
58 </p>`;
59 return getEmailShell(content);
60}
61
62function orderConfirmationEmail({ recipientName, orderNumber, amount, items }) {
63 const itemRows = items.map(item =>
64 `<tr>
65 <td style="padding:8px 0;border-bottom:1px solid #e5e7eb;">${item.name}</td>
66 <td style="padding:8px 0;border-bottom:1px solid #e5e7eb;
67 text-align:right;">$${item.price.toFixed(2)}</td>
68 </tr>`
69 ).join('');
70
71 const content = `
72 <p style="margin:0 0 16px;">Hi ${recipientName}, your order is confirmed!</p>
73 <p style="margin:0 0 24px;font-size:14px;color:#6b7280;">
74 Order #${orderNumber}
75 </p>
76 <table width="100%" cellpadding="0" cellspacing="0" border="0">
77 ${itemRows}
78 <tr>
79 <td style="padding:12px 0;"><strong>Total</strong></td>
80 <td style="padding:12px 0;text-align:right;">
81 <strong>$${amount.toFixed(2)}</strong>
82 </td>
83 </tr>
84 </table>`;
85 return getEmailShell(content);
86}
87
88module.exports = { welcomeEmail, orderConfirmationEmail };

Expected result: The emailTemplates.js utility exports two functions that return complete, self-contained HTML email strings ready to send.

3

Create the sendEmail Cloud Function with SendGrid

In your Cloud Functions index.js, import the emailTemplates utility and create a callable function named sendNotificationEmail. Store your SendGrid API key as a Firebase Function secret: run 'firebase functions:secrets:set SENDGRID_API_KEY' in your terminal. The function accepts the recipient email, template name, and template variables. It calls the appropriate template function to generate the HTML, then sends via the @sendgrid/mail package. Always include a plain-text version using the text parameter — this improves deliverability and provides a fallback for email clients that do not render HTML.

functions/index.js
1// functions/index.js (sendEmail portion)
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const sgMail = require('@sendgrid/mail');
5const { welcomeEmail, orderConfirmationEmail } = require('./emailTemplates');
6
7const templateMap = { welcome: welcomeEmail, order_confirmation: orderConfirmationEmail };
8
9exports.sendNotificationEmail = functions.https.onCall(async (data, context) => {
10 if (!context.auth) {
11 throw new functions.https.HttpsError('unauthenticated', 'Login required');
12 }
13
14 const { recipientEmail, templateName, templateVars } = data;
15 const templateFn = templateMap[templateName];
16
17 if (!templateFn) {
18 throw new functions.https.HttpsError('invalid-argument', `Unknown template: ${templateName}`);
19 }
20
21 sgMail.setApiKey(process.env.SENDGRID_API_KEY);
22
23 const html = templateFn(templateVars);
24
25 await sgMail.send({
26 to: recipientEmail,
27 from: { email: 'hello@yourapp.com', name: 'YourApp' },
28 subject: templateVars.subject || 'Notification from YourApp',
29 html,
30 text: `Hi ${templateVars.recipientName}, please view this email in an HTML-capable client.`,
31 });
32
33 return { success: true };
34});

Expected result: Calling sendNotificationEmail from FlutterFlow sends a branded HTML email via SendGrid. The email renders correctly in Gmail, Apple Mail, and Outlook.

4

Store email templates in Firestore for admin editing

Hardcoding templates in JavaScript means every change requires a Cloud Function redeployment. Instead, store your templates in Firestore so your admin can edit them from your app. Create an email_templates collection in Firestore. Each document has a name (String), subject (String), html_body (String — the inner HTML content, without the shell), and last_edited_by (String). In your Cloud Function, fetch the template document by name, inject the dynamic variables using string replacement (replace {{recipientName}} with the actual value), and wrap it in getEmailShell() before sending. Add a Template Editor page in FlutterFlow for your admin, using a multiline TextField bound to the html_body field.

Expected result: Admins can edit email template content and subject lines from within the FlutterFlow admin panel without touching any code or redeploying Cloud Functions.

5

Trigger notification emails from FlutterFlow actions

In FlutterFlow, create a Custom Action named triggerNotificationEmail. Inside it, call the sendNotificationEmail Cloud Function using FirebaseFunctions.instance.httpsCallable('sendNotificationEmail') with the recipient's email, template name, and any template variables. Add this action to the appropriate trigger points: after a user signs up (welcome email), after an order is completed (order confirmation), or when an admin posts an announcement. For high-volume sends (newsletters to all users), do not loop through users in FlutterFlow — instead create a Cloud Function that queries Firestore for the recipient list and sends in batches of 1,000.

Expected result: Completing key actions in your FlutterFlow app (sign-up, purchase, etc.) automatically triggers the corresponding branded notification email to the user.

Complete working example

email_base_template.html
1<!-- Base email template shell inline CSS, table layout, 600px max width -->
2<!-- Store the content section in Firestore; wrap with this shell in Cloud Function -->
3<!DOCTYPE html>
4<html lang="en">
5<head>
6 <meta charset="UTF-8">
7 <meta name="viewport" content="width=device-width, initial-scale=1.0">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge">
9 <title>{{subject}}</title>
10</head>
11<body style="margin:0;padding:0;background-color:#f4f4f5;">
12 <!--[if mso]>
13 <table width="600" align="center" cellpadding="0" cellspacing="0" border="0">
14 <tr><td>
15 <![endif]-->
16 <table width="100%" cellpadding="0" cellspacing="0" border="0"
17 style="background-color:#f4f4f5;min-width:100%;">
18 <tr>
19 <td align="center" style="padding:24px 16px;">
20 <table width="600" cellpadding="0" cellspacing="0" border="0"
21 style="max-width:600px;width:100%;background:#ffffff;
22 border-radius:8px;">
23 <!-- Logo header -->
24 <tr>
25 <td style="background-color:#4F46E5;padding:24px 32px;
26 border-radius:8px 8px 0 0;">
27 <img src="https://yourapp.com/logo-white.png"
28 alt="YourApp" width="120" height="auto"
29 style="display:block;border:0;">
30 </td>
31 </tr>
32 <!-- Content area - filled from Firestore template -->
33 <tr>
34 <td style="padding:32px;font-family:Arial,sans-serif;
35 font-size:16px;line-height:1.6;color:#374151;">
36 {{html_body}}
37 </td>
38 </tr>
39 <!-- Divider -->
40 <tr>
41 <td style="height:1px;background:#e5e7eb;"></td>
42 </tr>
43 <!-- Footer -->
44 <tr>
45 <td style="padding:20px 32px;background:#f9fafb;
46 border-radius:0 0 8px 8px;">
47 <table width="100%" cellpadding="0" cellspacing="0" border="0">
48 <tr>
49 <td style="font-family:Arial,sans-serif;font-size:12px;
50 color:#9ca3af;">
51 YourApp Inc., 123 Main St, City, ST 00000<br>
52 <a href="{{unsubscribe_url}}"
53 style="color:#6b7280;text-decoration:underline;">Unsubscribe</a>
54 &nbsp;&middot;&nbsp;
55 <a href="https://yourapp.com/privacy"
56 style="color:#6b7280;text-decoration:underline;">Privacy Policy</a>
57 </td>
58 </tr>
59 </table>
60 </td>
61 </tr>
62 </table>
63 </td>
64 </tr>
65 </table>
66 <!--[if mso]></td></tr></table><![endif]-->
67</body>
68</html>

Common mistakes when creating Custom Email Templates for Notifications in FlutterFlow

Why it's a problem: Using CSS div layouts and external stylesheets in email HTML

How to avoid: Use HTML tables for layout and inline styles on every element. Write style='...' directly on each tag. Never use classes, IDs, or link tags to external CSS in email HTML.

Why it's a problem: Hardcoding email templates in Cloud Function code and redeploying for every copy change

How to avoid: Store template HTML in Firestore documents. The Cloud Function fetches the template at send time. Non-technical team members can edit templates from the admin panel without touching code.

Why it's a problem: Sending high-volume emails by looping in a FlutterFlow action

How to avoid: For bulk email sends, create a Cloud Function that queries the recipient list from Firestore and sends in batches of 1,000 using SendGrid's batch API. Trigger this single function from the app.

Why it's a problem: Not setting up domain authentication (SPF, DKIM, DMARC) before sending transactional emails

How to avoid: In SendGrid, go to Settings → Sender Authentication → Domain Authentication. Follow the step-by-step DNS record setup for your domain. Verify before sending any production emails.

Best practices

  • Always use inline CSS and HTML table layouts — external stylesheets and div layouts break in Gmail and Outlook.
  • Store templates in Firestore so non-technical team members can edit copy without code changes or redeployment.
  • Set up domain authentication (SPF, DKIM, DMARC) before sending any production emails to avoid the spam folder.
  • Include both HTML and plain text versions in every email send — plain text improves deliverability and accessibility.
  • Log every email send to an email_logs Firestore collection for debugging and compliance auditing.
  • Include an unsubscribe link in every marketing email — this is legally required under CAN-SPAM (US) and GDPR (EU).
  • Test templates in at least Gmail, Apple Mail, and Outlook before deploying — use Litmus or Email on Acid for multi-client testing.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a FlutterFlow app and want to send branded HTML notification emails via SendGrid from Firebase Cloud Functions. Show me a complete email template using inline CSS and table layouts that renders correctly in Gmail and Outlook. Explain why div layouts fail in email clients.

FlutterFlow Prompt

Create a Firebase Cloud Function that fetches an email template from a Firestore email_templates collection, replaces {{variable}} placeholders with actual values, and sends the resulting HTML email via SendGrid. The function should accept recipientEmail, templateName, and a templateVars object.

Frequently asked questions

Why can I not use CSS classes or external stylesheets in email HTML?

Gmail strips all CSS classes and most style tags in the head section of emails. Outlook uses Microsoft Word's HTML renderer which does not support modern CSS. The only CSS that works reliably across all major email clients is inline styles written directly on each HTML element.

Which email sending service should I use — SendGrid, Mailgun, or Resend?

SendGrid is the most widely used, has a free tier of 100 emails per day, and has the best documentation. Mailgun has competitive pricing for high volume. Resend is a newer option popular with developers for its clean API. All three work with Firebase Cloud Functions — the integration code is nearly identical.

How do I make email templates editable without redeploying Cloud Functions?

Store the template HTML as a string in a Firestore document. The Cloud Function fetches the template at send time using the template name, replaces placeholders like {{name}} with actual values, wraps it in your HTML shell, and sends. When the template needs updating, edit the Firestore document — no code change required.

How do I add a logo image to my email template?

Upload your logo to Firebase Storage and get its public download URL. Use that URL in an img tag with explicit width and height attributes and style='display:block;border:0;' to prevent spacing issues in Outlook. Never use base64-encoded images — they bloat the email size and are often blocked.

What is the maximum email size to avoid spam filters?

Keep emails under 100KB total (HTML + images). Emails over 100KB are clipped by Gmail and shown with a 'Message clipped' warning. Most transactional notification emails should be well under this limit.

Do I need FlutterFlow Pro to send notification emails?

No. The email sending happens in Firebase Cloud Functions, not in the FlutterFlow app. You need the Firebase Blaze plan for Cloud Functions, but FlutterFlow's Free plan is sufficient to trigger the function via a Custom Action.

How do I handle email bounces and spam complaints?

SendGrid provides webhooks for bounce and spam complaint events. Create a Firebase Cloud Function HTTP endpoint that receives these webhooks and updates the corresponding user document in Firestore (set emailBounced: true or emailUnsubscribed: true). Check these flags before sending future emails to prevent sending to invalid addresses.

RapidDev

Talk to an Expert

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

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

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.