Skip to main content

Command Palette

Search for a command to run...

Automating user onboarding: Flutterflow + Supabase + Loops email integration

See how three powerful tools work together to create professional user onboarding flows

Updated
15 min read
Automating user onboarding: Flutterflow + Supabase + Loops email integration

Introduction

I frequently build mobile and web applications using No Code and Low Code tools. Through my journey, I've documented various integration patterns in my No Code & Low Code blog series. Recently, I've been deep into FlutterFlow development with Supabase as the backend, and one challenge kept coming up: automating the user onboarding email flow with temporary passwords.

Last week, I was building an application where administrators needed to create user accounts on behalf of customers. The flow seemed straightforward—admin creates account, user receives email with credentials, user confirms email and logs in. However, connecting FlutterFlow's signup action with Supabase's authentication system and Loops' email service turned out to be more nuanced than expected.

The breakthrough came when I realized that Supabase's email confirmation system could be configured to use Loops as the SMTP provider, and user metadata could be passed through to email templates. This discovery transformed a potentially complex multi-step process into an elegant, automated flow.

Having refined this integration pattern, I decided to write this tutorial-style blog post to document the exact steps involved, hoping it might help someone (including future me 😉) who needs to implement a similar onboarding system.

Let's get going 🛹

A Bit of Context

In the application I'm building, administrators create accounts for customers who will then use the platform. The business requirements were clear:

  1. Admin creates the account with the user's email and basic information

  2. System generates a random temporary password

  3. User receives an email with their credentials and a confirmation link

  4. User clicks the confirmation link to verify their email address

  5. User logs in with the temporary password

  6. System forces password change on first login for security

From a technical perspective, this involves orchestrating three different systems:

  • FlutterFlow: The frontend where admins create accounts

  • Supabase: The backend handling authentication and database

  • Loops: The email service provider for transactional emails

The key challenge was passing the randomly generated password from FlutterFlow through Supabase to Loops, while maintaining proper authentication flows and security practices.

💡 Why this stack? FlutterFlow offers rapid mobile/web development, Supabase provides robust authentication with row level security, and Loops excels at beautiful transactional emails. Together, they create a powerful, scalable onboarding system.

What We're Building Today

We're going to build a complete user onboarding system that:

  1. Generates random temporary passwords in FlutterFlow

  2. Creates user accounts in Supabase Auth

  3. Stores user profiles with metadata

  4. Sends branded confirmation emails via Loops

  5. Handles email verification and redirects

  6. Forces password changes on first login

This real-world scenario covers the entire authentication flow from account creation to secure first login.

Prerequisites

Important: This tutorial assumes you already have working knowledge of FlutterFlow and Supabase. If you are brand new to either platform, I recommend getting familiar with the basics first before tackling this integration.

Before we begin, make sure you have:

  • FlutterFlow project set up with Supabase integration

  • Supabase project with authentication enabled

  • Loops account with SMTP credentials

  • Basic understanding of FlutterFlow custom actions

  • Familiarity with Supabase authentication concepts

Part 1: Setting Up Loops SMTP in Supabase

This is the foundation that makes everything work. We're going to configure Supabase to send all authentication emails through Loops.

Step 1: Get Your Loops SMTP Credentials

  1. Log in to your Loops dashboard

  2. Navigate to Settings → Supabase

  3. Click on Connect Supabase in the SMTP section

Step 2: Connect Loops to Your Supabase Project

Once you're on the Loops Supabase integration screen, here's what happens:

  1. Authorize Supabase access

    • Click to connect your Supabase account

    • Supabase will prompt you to select your organization

    • Click "Authorize" to grant Loops access

  2. Configure the connection

    • Back in Loops, select your Supabase project from the dropdown

    • Choose an API key (this becomes your SMTP password)

    • Click "Set up SMTP"

  3. Verify the setup

    • Loops automatically adds its SMTP credentials to your Supabase project

    • You'll see a success message: ✓ "SMTP has been set up in your Supabase project"

Behind the scenes, Loops configures these SMTP settings in your Supabase project:

  • Host: smtp.loops.so

  • Port: 587

  • Username: loops

  • Password: One of your Loops API keys

You can verify these credentials anytime in Supabase by going to Authentication → Emails → SMTP Settings.

💡 Note: The API key you select becomes your SMTP password. You can find and manage your API keys in Loops under Settings → API.

Step 3: Configure Supabase SMTP Settings

  1. Open your Supabase project dashboard

  2. Go to Project Settings → Authentication → SMTP Settings

  3. Enable Custom SMTP

  4. Fill in the Loops credentials:

    • Host: Your Loops SMTP host

    • Port number: 587

    • Sender email: The email address you want emails to come from

    • Sender name: Your application name (e.g., "Your App Team")

    • Username: Loops SMTP username

    • Password: Loops SMTP password

Note: Sender email address and name can be overwritten in the Loops dashboard.

  1. Click Save

💡 Important: Test your SMTP connection by creating a test user. If emails aren't sending, double-check your Loops credentials and ensure your sender email is verified in Loops.

Part 2: Creating the Email Template in Loops

Now let's design the email that users will receive with their credentials.

Step 1: Create a New Transactional Email

  1. In Loops, go to Transactional → Create transactional email

  2. Name it something like "User Welcome & Email Confirmation"

  3. Design your email with these elements:

    • Greeting

    • Welcome message

    • Temporary credentials section

    • Security note about password change

    • Confirmation button

    • Signature

Here's the structure I use:

Hello,

Thank you for joining [Your App Name]. To get started, please use these credentials to log in:

Email: {{customer_email}}
Password: {{generated_password}}

Before you can access your account, please verify your email address by clicking the button below.

[Confirm my account button]

Important: You'll need to create a new password when you log in for the first time.

Regards,
The [Your App Name] Team

Step 2: Define Data Variables

In your Loops template, you need to define three data variables:

  1. customer_email - The user's email address

  2. generated_password - The temporary password

  3. confirmationURL - Supabase's confirmation link

These variables will be populated dynamically when Supabase triggers the email.

Step 3: Get Your Transactional ID

After saving your template, Loops will generate a transactional ID. This is a unique identifier you'll use in Supabase to reference this specific email template.

Copy this ID—you'll need it in the next section.

Part 3: Configuring Supabase Email Template

This is where we connect Supabase's authentication system to your Loops template.

Step 1: Access Email Templates

  1. In Supabase dashboard, go to Project Settings → Authentication → Email

  2. Click on Confirm signup template

Step 2: Configure the Template for Loops

Replace the default template with this JSON structure:

{
  "transactionalId": "your_loops_transactional_id_here",
  "email": "{{.Email}}",
  "dataVariables": {
    "confirmationURL": "{{.ConfirmationURL}}",
    "customer_email": "{{.Email}}",
    "generated_password": "{{.Data.password}}"
  }
}

Let me break down what each part does:

  • transactionalId: Your Loops template ID (paste the one you copied earlier)

  • email: Supabase variable for the user's email address

  • dataVariables: Object containing all dynamic values for your Loops template

    • confirmationURL: Supabase's auto-generated confirmation link

    • customer_email: Passes the email to Loops

    • generated_password: This is the critical part—it accesses the password we'll pass via user metadata

💡 Key insight: Notice we're using {{.Data.password}} not {{.UserMetaData.password}}. This accesses the user metadata object correctly in Supabase's template system.

Part 4: Building the FlutterFlow Custom Action

Now we'll create the FlutterFlow action that creates users and passes the password through to the email.

Step 1: Generate Random Password

First, create a custom action or use FlutterFlow's action flow to generate a random password. You can use a formula or custom code like:

String generateRandomPassword(int length) {
  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  final random = Random.secure();
  return List.generate(length, (index) => chars[random.nextInt(chars.length)]).join();
}

I kept it simple, just made use a of a formula


Step 2: Create the Custom Action for User Creation

Create a custom action called createCustomerAccountAction with these parameters:

Input Parameters:

  • email (String)

  • password (String) - The generated random password

  • name (String)

  • phonenumber (String)

  • category (String) - User type/category

  • role (String) - User role

Return Type: String (success/error message)


Step 3: Implement the Custom Action Code

Here's the complete code for the custom action:

Future<String> createCustomerAccountAction(
  String email,
  String password,
  String name,
  String phonenumber,
  String category,
  String role
) async {
  try {
    final response = await SupaFlow.client.auth.signUp(
      email: email,
      password: password,
      data: {
        'name': name,
        'phone_number': phonenumber,
        'category': category,
        'role': role,
        'password': password,  // THIS IS CRITICAL - Pass password in metadata
      },
    );

    if (response.user != null) {
      return 'Customer added successfully';
    } else {
      return 'Customer account creation failed!';
    }
  } catch (e) {
    return 'Error: ${e.toString()}';
  }
}

The crucial part: Notice we're passing password inside the data object. This stores it in the user's metadata (auth.users.raw_user_meta_data), making it accessible to our email template via {{.Data.password}}.

💡 Security note: The password in metadata is just for sending via email. Supabase automatically hashes the actual password (the one outside the data object) and stores it securely in auth.users.encrypted_password. The metadata password is only used for the email and can be removed after the user changes their password.


Step 4: Add This Action to Your Admin Interface

In your FlutterFlow admin screen where you create users:

  1. Add form fields for email, name, phone, category, role

  2. Add a button "Create User"

  3. On button click, call your custom action:

    • Generate random password first (If you’re using a simple formula, then you don’t need this)

    • Pass all parameters including the generated password

    • Show success/error message


Part 5: Setting Up the User Profile Table

When Supabase creates a user in auth.users, it only stores authentication-related data (email, password hash, metadata). For your application, you'll likely need additional user information like name, phone number, role, and status in a separate table that you can query and manage easily.

We'll create a user_profiles table in the public schema to store this data, then set up a database trigger that automatically creates a profile record whenever a new user signs up.

Why do we need a separate user_profiles table?

  • auth.users is managed by Supabase and has limited columns - you can't add custom fields

  • user_profiles gives you full control to store application-specific data

  • It enables better Row Level Security policies for your app

  • You can query and join user data with other tables easily

  • It keeps authentication separate from application logic

Step 1: Create the user_profiles Table

In Supabase SQL Editor, create your user profiles table:

CREATE TABLE public.user_profiles (
  id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  name text,
  phone_number text,
  category text,
  role text,
  status text DEFAULT 'inactive',
  created_at timestamp with time zone DEFAULT now(),
  updated_at timestamp with time zone DEFAULT now()
);

-- Enable Row Level Security
ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;

Step 2: Create the Trigger Function

Create a function that automatically creates a user profile when a user signs up:

CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger AS $$
BEGIN
  INSERT INTO public.user_profiles (
    id,
    name,
    phone_number,
    category,
    role,
    status
  )
  VALUES (
    NEW.id,
    NEW.raw_user_meta_data->>'name',
    NEW.raw_user_meta_data->>'phone_number',
    NEW.raw_user_meta_data->>'category',
    NEW.raw_user_meta_data->>'role',
    'inactive'
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

Step 3: Create the Trigger

CREATE TRIGGER on_auth_user_created
  AFTER INSERT ON auth.users
  FOR EACH ROW
  EXECUTE FUNCTION public.handle_new_user();

This trigger automatically fires whenever a new user is created in auth.users, pulling data from the metadata and creating a corresponding profile record.

Part 6: Configuring Email Confirmation Redirect

After users click "Confirm my account", they need to be redirected back to your application.

Step 1: Set Up Redirect URLs in Supabase

  1. Go to Authentication → URL Configuration

  2. Set your Site URL to your application's URL (e.g., https://yourapp.flutterflow.app)

  3. Add Redirect URLs - these are the allowed URLs Supabase can redirect to after confirmation

💡 For FlutterFlow: Your redirect URL will typically be your published app URL. F

Part 7: Implementing First Login Password Change

Now we need to ensure users change their password on first login.

Step 1: Check User Status on Login

In your FlutterFlow login flow, after successful authentication:

  1. Query the user_profiles table for the current user

  2. Check the status field

  3. If status is 'inactive', redirect to password change screen

  4. If status is 'active', redirect to main dashboard

Step 2: Create Password Change Screen

Build a simple screen with:

  • Password input field (with validation)

  • Confirm password input field

  • "Update Password" button

On button click:

  1. Validate passwords match

  2. Call Supabase's updateUser function to change password

  3. Update user status to 'active' in user_profiles table

  4. Navigate to dashboard

Step 3: Update User Status

Create a custom action or backend query to update the status:

Future<void> updateUserStatusToActive(String userId) async {
  await SupaFlow.client
    .from('user_profiles')
    .update({'status': 'active'})
    .eq('id', userId);
}

Part 8: Testing the Complete Flow

Time to test everything end-to-end!

Test Checklist:

  1. Admin creates user

    • Fill out the form with test data

    • Generate random password

    • Submit

  1. Verify user in Supabase

    • Check auth.users table for new user

    • Check user_profiles table for matching record

    • Verify status is 'inactive'

  1. Check email delivery

    • Email should arrive with correct credentials

    • Verify password is visible

    • Check confirmation button works

  1. Click confirmation link

    • User should be redirected to your app

    • Email confirmation status should update in Supabase

  2. Test first login

    • Use credentials from email

    • Should redirect to password change screen (not dashboard)

  1. Change password

    • Set new password

    • Verify status updates to 'active'

    • Should redirect to dashboard

  2. Test second login

    • Login with new password

    • Should go directly to dashboard


Common Issues and Troubleshooting

Issue 1: "Error sending confirmation email"

Cause: Usually a problem with the Loops template configuration or SMTP settings.

Solutions:

  • Verify your Loops SMTP credentials in Supabase are correct

  • Check that your Loops transactional ID is accurate

  • Ensure all data variables in Loops match what you're sending from Supabase

  • Look at Supabase Auth logs for specific error messages

Issue 2: Password not showing in email

Cause: Password not being passed correctly through metadata.

Solutions:

  • Verify you're using {{.Data.password}} not {{.UserMetaData.password}} in template

  • Check that password is included in the data object in your FlutterFlow action

  • Test by checking auth.users.raw_user_meta_data in Supabase to see if password is there

Issue 3: User can't log in after confirmation

Cause: Email might not be properly confirmed in Supabase.

Solutions:

  • Check auth.users.email_confirmed_at field—should have a timestamp

  • Verify redirect URLs are set correctly in Supabase

  • Ensure the confirmation link format matches Supabase requirements

Issue 4: Missing comma in Supabase template

Cause: JSON syntax error in email template configuration.

Solution: Ensure every line except the last one in objects has a comma:

{
  "transactionalId": "xxx",
  "email": "{{.Email}}",          // comma here
  "dataVariables": {
    "confirmationURL": "{{.ConfirmationURL}}",  // comma here
    "customer_email": "{{.Email}}",              // comma here
    "generated_password": "{{.Data.password}}"   // no comma on last item
  }
}

Security Considerations

While this pattern works well for controlled environments, here are some security best practices:

1. Password Transmission

The risk: Passwords are sent via email, which is inherently less secure than other methods.

Mitigation:

  • Passwords are temporary and must be changed on first login

  • Email communication is over TLS/SSL

  • API calls are over HTTPS

  • For high-security applications, consider using "set password" links instead

2. Metadata Storage

The risk: Password stored in user metadata is in plaintext.

Mitigation:

  • This is only temporary for email sending

  • The actual authentication password is properly hashed by Supabase

  • Consider removing the metadata password after first login or after a time period

3. Email Interception

The risk: If someone intercepts the email, they get temporary access.

Mitigation:

  • Force password change on first login

  • Consider adding two-factor authentication

  • Monitor for suspicious login attempts

  • Set short expiration times for confirmation links


Pro Tips from My Experience

1. Always Check Supabase Logs

When debugging email issues, Authentication → Logs is your best friend. It shows exactly why an email failed to send.

2. Use Descriptive Variable Names

Instead of temp_pass, use generated_password. Your future self will thank you when debugging.

3. Test with Real Email Addresses

Don't rely solely on test accounts. Send to your actual email to see the real user experience.

4. Version Your Email Templates

Keep track of changes to your Loops templates. Loops has built-in versioning—use it.

5. Handle Edge Cases

What happens if:

  • User clicks confirmation link multiple times?

  • User never confirms email?

  • Password generation fails?

Build error handling for these scenarios.

6. Monitor Email Deliverability

Check your Loops dashboard regularly for bounce rates and delivery issues. Email deliverability can degrade over time if not monitored.


Extending This Pattern

Once you have this basic flow working, you can extend it for other use cases:

Password Reset Emails

Use the same Loops + Supabase pattern for password reset emails with custom branding.

Welcome Sequences

After confirmation, trigger a series of onboarding emails through Loops.

Role-Based Onboarding

Send different email templates based on user role or category stored in metadata.

Multi-Tenant Systems

Customize email branding based on the organization the user belongs to.


Conclusion

Connecting FlutterFlow, Supabase, and Loops for automated user onboarding creates a professional, scalable system that handles the complete authentication flow. By passing user metadata through Supabase's authentication system to Loops templates, we can send beautifully branded emails with dynamic content like temporary passwords.

This pattern works for any scenario where administrators create user accounts on behalf of customers. From SaaS platforms to membership sites to internal business applications, this integration pattern provides a robust foundation for user onboarding.

The key insight is understanding how data flows through the system:

  1. FlutterFlow generates and passes data to Supabase

  2. Supabase creates the user and triggers email

  3. Email flows through Loops SMTP with dynamic variables

  4. User confirms and logs in

  5. First login flow ensures security with password change

Once you master this pattern, you'll find yourself using it across multiple projects. It's one of those integrations that seems complex at first but becomes second nature once you understand how the pieces fit together.

I've implemented this pattern across several client projects involving member management systems, professional service platforms, and collaborative tools.

Need help building your next application? Whether you're developing member portals, SaaS platforms, or business management systems, I can help you architect and build complete solutions with FlutterFlow, Supabase, and the entire modern no-code stack.

Ciao until next time! 👋


P.S. I build internal tools & SaaS products for clients using tools like FlutterFlow, Supabase, WeWeb, Xano, n8n and more. Have an idea you want to bring to life or need help building software? Send me a message on LinkedIn or DM me on X.

No Code & Low Code

Part 2 of 21

In this series, I write about the world of No Code & Low Code tools, their applications, how to use them. It may not be a continuation of one other, but they fall under the category of No Code & Low Code.

Up next

Why Xano's create object is essential for database updates

Build scalable update systems that handle calculations, validations, and complex business logic