Automating user onboarding: Flutterflow + Supabase + Loops email integration
See how three powerful tools work together to create professional user onboarding flows

Search for a command to run...
See how three powerful tools work together to create professional user onboarding flows

No comments yet. Be the first to comment.
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.
Build scalable update systems that handle calculations, validations, and complex business logic
Some thoughts on why I built WorkBuddy, from someone who hasn't written here in a while

From content personalization to lead capture: the automation patterns that solve real business problems

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

A step-by-step guide to updating data seamlessly by integrating Xano's backend with WeWeb's front-end

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 🛹
In the application I'm building, administrators create accounts for customers who will then use the platform. The business requirements were clear:
Admin creates the account with the user's email and basic information
System generates a random temporary password
User receives an email with their credentials and a confirmation link
User clicks the confirmation link to verify their email address
User logs in with the temporary password
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.
We're going to build a complete user onboarding system that:
Generates random temporary passwords in FlutterFlow
Creates user accounts in Supabase Auth
Stores user profiles with metadata
Sends branded confirmation emails via Loops
Handles email verification and redirects
Forces password changes on first login
This real-world scenario covers the entire authentication flow from account creation to secure first login.
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
This is the foundation that makes everything work. We're going to configure Supabase to send all authentication emails through Loops.
Log in to your Loops dashboard
Navigate to Settings → Supabase
Click on Connect Supabase in the SMTP section
Once you're on the Loops Supabase integration screen, here's what happens:
Authorize Supabase access
Click to connect your Supabase account
Supabase will prompt you to select your organization
Click "Authorize" to grant Loops access
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"
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.
Open your Supabase project dashboard
Go to Project Settings → Authentication → SMTP Settings
Enable Custom SMTP
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.

💡 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.
Now let's design the email that users will receive with their credentials.
In Loops, go to Transactional → Create transactional email
Name it something like "User Welcome & Email Confirmation"
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
In your Loops template, you need to define three data variables:
customer_email - The user's email address
generated_password - The temporary password
confirmationURL - Supabase's confirmation link
These variables will be populated dynamically when Supabase triggers the email.
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.

This is where we connect Supabase's authentication system to your Loops template.
In Supabase dashboard, go to Project Settings → Authentication → Email
Click on Confirm signup template
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.
Now we'll create the FlutterFlow action that creates users and passes the password through to the email.
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

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)
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.
In your FlutterFlow admin screen where you create users:
Add form fields for email, name, phone, category, role
Add a button "Create User"
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
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.
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
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;
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;
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.
After users click "Confirm my account", they need to be redirected back to your application.
Go to Authentication → URL Configuration
Set your Site URL to your application's URL (e.g., https://yourapp.flutterflow.app)
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
Now we need to ensure users change their password on first login.
In your FlutterFlow login flow, after successful authentication:
Query the user_profiles table for the current user
Check the status field
If status is 'inactive', redirect to password change screen
If status is 'active', redirect to main dashboard
Build a simple screen with:
Password input field (with validation)
Confirm password input field
"Update Password" button
On button click:
Validate passwords match
Call Supabase's updateUser function to change password
Update user status to 'active' in user_profiles table
Navigate to dashboard

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);
}
Time to test everything end-to-end!
Admin creates user ✓
Fill out the form with test data
Generate random password
Submit
Verify user in Supabase ✓
Check auth.users table for new user
Check user_profiles table for matching record
Verify status is 'inactive'
Check email delivery ✓
Email should arrive with correct credentials
Verify password is visible
Check confirmation button works
Click confirmation link ✓
User should be redirected to your app
Email confirmation status should update in Supabase
Test first login ✓
Use credentials from email
Should redirect to password change screen (not dashboard)
Change password ✓
Set new password
Verify status updates to 'active'
Should redirect to dashboard
Test second login ✓
Login with new password
Should go directly to dashboard
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
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
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
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
}
}
While this pattern works well for controlled environments, here are some security best practices:
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
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
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
When debugging email issues, Authentication → Logs is your best friend. It shows exactly why an email failed to send.
Instead of temp_pass, use generated_password. Your future self will thank you when debugging.
Don't rely solely on test accounts. Send to your actual email to see the real user experience.
Keep track of changes to your Loops templates. Loops has built-in versioning—use it.
What happens if:
User clicks confirmation link multiple times?
User never confirms email?
Password generation fails?
Build error handling for these scenarios.
Check your Loops dashboard regularly for bounce rates and delivery issues. Email deliverability can degrade over time if not monitored.
Once you have this basic flow working, you can extend it for other use cases:
Use the same Loops + Supabase pattern for password reset emails with custom branding.
After confirmation, trigger a series of onboarding emails through Loops.
Send different email templates based on user role or category stored in metadata.
Customize email branding based on the organization the user belongs to.
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:
FlutterFlow generates and passes data to Supabase
Supabase creates the user and triggers email
Email flows through Loops SMTP with dynamic variables
User confirms and logs in
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.