# 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](https://www.flutterflow.io/) development with [Supabase](https://supabase.com/) 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](https://loops.so/)' 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`](http://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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1766076159665/2fe79760-ab48-4130-b4d0-ac2b6cc69f31.png align="center")

5. 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:

```plaintext
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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1766141578811/5e38641e-a7be-4c3f-8883-1288977e86b0.png align="center")

## 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:

```json
{
  "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
        

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1766141793879/afddbe90-f888-447c-be8a-c9044fa3fccc.png align="center")

💡 **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:

```dart
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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1766141920783/95f226f3-e1d0-466c-90c6-43f290b4273b.png align="center")

---

### 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:

```dart
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:

```sql
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:

```sql
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

```sql
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`](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
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1766146500811/a6e78ba9-35c3-43e5-a174-a656c4634b78.png align="center")
    

### Step 3: Update User Status

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

```dart
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
        

2. **Verify user in Supabase** ✓
    
    * Check `auth.users` table for new user
        
    * Check `user_profiles` table for matching record
        
    * Verify status is `'inactive'`
        

3. **Check email delivery** ✓
    
    * Email should arrive with correct credentials
        
    * Verify password is visible
        
    * Check confirmation button works
        

4. **Click confirmation link** ✓
    
    * User should be redirected to your app
        
    * Email confirmation status should update in Supabase
        
5. **Test first login** ✓
    
    * Use credentials from email
        
    * Should redirect to password change screen (not dashboard)
        

6. **Change password** ✓
    
    * Set new password
        
    * Verify status updates to `'active'`
        
    * Should redirect to dashboard
        
7. **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`](http://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:

```json
{
  "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](https://www.flutterflow.io/), [Supabase](https://supabase.com/), [WeWeb](https://weweb.io), [Xano](https://xano.com), [n8n](https://n8n.io/) and more. Have an idea you want to bring to life or need help building software? Send me a message on [LinkedIn](https://linkedin.com/in/sambhavi-dhanabalan) or DM me on [X](https://x.com/saminsolitude).
