Firebase email guides
Firebase transactional email options
Firebase is great for building apps quickly, but transactional email is still a slightly awkward area.
Firebase gives you some useful email features, especially around Authentication, and there is an official Trigger Email extension. But Firebase does not really give you one complete general-purpose transactional email product in the same way it gives you Auth, Firestore, Hosting, Storage and Functions.
That leaves Firebase developers with a few different routes.
Some are fast but limited.
Some are flexible but involve more setup.
Some keep email inside Firebase.
Some move email into a dedicated provider or template-first service.
This guide compares the main transactional email options for Firebase apps and explains which one to choose depending on how much email infrastructure you want to own.
The short version
There are four common ways to handle transactional email in a Firebase app:
- Use Firebase Authentication’s built-in emails.
- Use the Firebase Trigger Email extension.
- Use Cloud Functions with an email provider.
- Use Cloud Functions with EmailsDone.
They all solve slightly different problems.
| Option | Best for | Main trade-off |
|---|---|---|
| Firebase Auth emails | Basic auth flows | Limited to Firebase Auth use cases |
| Firebase Trigger Email extension | Firestore-to-SMTP email triggering | You still manage SMTP and email content |
| Cloud Functions with an email provider | Full control | You own templates, HTML, provider setup and delivery plumbing |
| Cloud Functions with EmailsDone | Common app emails with less setup | Less flexible by design |
The important question is not just:
How do I send an email?
The better question is:
How much of the email layer do I want to own?
What counts as transactional email?
Transactional email is email sent because something happened in your app.
Examples include:
- welcome emails
- email verification
- password reset
- magic links
- login codes
- account invitations
- notifications
- billing emails
- payment failed emails
- trial ending emails
- invoice emails
- export ready emails
- report ready emails
These are not newsletters or marketing campaigns.
They are app emails.
Most of them are boring in a good way. They need to be clear, reliable and consistent. They do not usually need to be unique works of design.
That distinction matters when choosing an email approach.
Option 1: Firebase Authentication built-in emails
Firebase Authentication includes some email flows out of the box.
For example, Firebase can send password reset emails and email verification messages.
A password reset request can be as simple as:
import { getAuth, sendPasswordResetEmail } from "firebase/auth";
const auth = getAuth();
await sendPasswordResetEmail(auth, "user@example.com");
That is hard to beat for speed.
Firebase handles the account recovery flow, sends the email, and gives you a working reset experience without setting up a separate provider.
This is often the right choice when you are early.
Where it works well
Use Firebase Auth emails when:
- you only need basic auth emails
- you want the fastest working setup
- Firebase’s default flow is good enough
- you do not need detailed email delivery visibility
- you are happy configuring templates in the Firebase Console
For a prototype or early app, this is a sensible default.
Where it becomes limiting
The built-in Auth emails are tied to Firebase Auth flows.
They do not solve general transactional email for your app.
You may still need:
- welcome emails
- app notifications
- team invitations
- billing emails
- payment failed emails
- export ready emails
You may also find that the email templates live away from the rest of your app email setup, inside the Firebase Console.
That is not wrong, but it is another place to maintain.
Option 2: Firebase Trigger Email extension
Firebase also has an official Trigger Email extension.
This is best thought of as a Firestore-to-SMTP bridge.
Your app writes an email document into Firestore. The extension watches that collection and sends the email using the SMTP provider you configured.
The flow looks like this:
Your Firebase app
↓
Firestore email document
↓
Firebase Trigger Email extension
↓
Configured SMTP provider
↓
Recipient inbox
This can be a neat Firebase-native pattern.
It feels natural if your app already uses Firestore as a way to queue backend work.
Where it works well
Use Firebase Trigger Email when:
- you want a Firebase extension
- you like Firestore-driven workflows
- you already have an SMTP provider
- you want a mail collection acting as a simple queue
- you are happy owning the templates and email content
- you want flexibility over the message structure
For some apps, writing a Firestore document and letting the extension send it is a good fit.
Where the work remains
Trigger Email helps with the trigger mechanism.
It does not remove the whole email problem.
You still need to think about:
- SMTP provider setup
- sender configuration
- email templates
- HTML and text content
- template variables
- delivery behaviour
- provider limits
- monitoring failures
- duplicate sends
- app-specific email structure
That is fine if you want to own those things.
But it is not the same as having the common app emails already packaged for you.
Option 3: Cloud Functions with an email provider
Another common route is to call an email provider directly from Firebase Cloud Functions.
The flow looks like this:
Frontend app
↓
Firebase Cloud Function
↓
Email provider API
↓
Recipient inbox
This is the flexible route.
You can use a provider such as Resend, Postmark, Mailgun, SendGrid, SES or another transactional email API.
A simplified provider call might look like this:
await provider.send({
to: "user@example.com",
subject: "Welcome to Acme",
html: `
<h1>Welcome</h1>
<p>Thanks for signing up.</p>
<a href="${actionUrl}">Get started</a>
`
});
That gives you control.
It also gives you responsibility.
Where it works well
Use Cloud Functions with an email provider when:
- you want maximum flexibility
- you already have your own templates
- you care about custom HTML and layout
- you have unusual email requirements
- you want direct control over the provider API
- you are happy managing the email layer yourself
This is a good route for teams that want a blank canvas.
Where it becomes work
The API call is usually not the hard part.
The hard part is everything around it:
- creating every template
- writing responsive HTML
- maintaining consistent styling
- handling template variables
- testing across email clients
- managing preview text
- dealing with buttons and fallback links
- keeping app emails consistent
- deciding where templates live
- preventing email code being scattered across functions
A provider gives you power.
It does not necessarily give you finished app emails.
Option 4: Cloud Functions with EmailsDone
EmailsDone uses the same safe backend pattern, but takes a more opinionated approach to the email layer.
Your Firebase app calls a Firebase Function.
The function validates the request and sends the email through EmailsDone.
The flow looks like this:
Frontend app
↓
Firebase Cloud Function
↓
EmailsDone template API
↓
Recipient inbox
The API key stays server-side.
The function stays under your control.
The email template is already done.
For example:
await emailsDone
.authentication()
.welcome("https://app.example.com/get-started")
.send("user@example.com");
Or:
await emailsDone
.authentication()
.passwordReset(resetUrl)
.send("user@example.com");
Or:
await emailsDone
.authentication()
.verifyEmail(verificationUrl)
.send("user@example.com");
This is the template-first route.
Instead of starting with a blank HTML email, you choose the app event and pass the data that template needs.
Where it works well
Use EmailsDone when:
- you want common app emails working quickly
- you do not want to design every email
- you do not want to write HTML templates
- you are building from Firebase Functions or trusted backend code
- you want app emails to feel consistent
- you want the sending layer handled without SMTP setup
- you prefer a constrained product that removes decisions
This is not the maximum-flexibility route.
That is the point.
Why not just use a blank email API?
Most transactional email APIs give you a blank canvas.
That is powerful, but it also means you own the email product layer.
You need to think about:
- HTML
- responsive layout
- template variables
- dark mode quirks
- preview text
- button styling
- fallback links
- wording
- consistency
- where templates live
- who maintains them
EmailsDone takes the opposite approach.
It is template-first on purpose.
For common app emails such as welcome, verify email, password reset, login code, notification and billing messages, the structure is already known. The app usually needs to provide the important data, not design a unique email from scratch.
Most apps do not need a unique password reset email.
They need a clear, safe, production-ready password reset email that works.
That constraint has benefits:
- faster integration
- fewer templates to maintain
- less HTML to debug
- safer agent-generated implementations
- more consistent app emails
- less room for spammy or abusive content
- simpler onboarding for Firebase developers
This is not the right trade-off for every product.
If you need complete creative control, use a general email provider directly.
If you want production app emails without turning email into another project, use a template-first approach.
Freedom vs finished
A lot of the Firebase email decision comes down to this:
Do you want freedom, or do you want finished?
A general email provider gives you freedom.
You can send almost anything. You can build your own templates, layouts, custom designs and workflows.
But you also own that work.
EmailsDone gives you something more finished.
You get less freedom over the email structure, but you avoid the faff of creating every common app email yourself.
Neither approach is universally better.
They are different products for different jobs.
| Approach | Flexibility | What you own |
|---|---|---|
| Firebase Auth emails | Low | Basic Firebase Auth email configuration |
| Firebase Trigger Email | Medium | SMTP setup, Firestore mail docs, templates |
| Provider API | High | HTML, templates, provider integration, layout |
| EmailsDone | Lower by design | App flow and template data |
The blank canvas is useful when email is part of your product.
The finished shelf is useful when email is just something your product needs.
Recommended route by app stage
Prototype or early Firebase app
Start with Firebase Auth emails for password reset and verification.
They work, they are quick, and they avoid adding another moving part too early.
Firebase app that needs a few custom emails
If you only need one or two simple messages and you already have an SMTP provider, Firebase Trigger Email can be a reasonable option.
It keeps the trigger inside Firebase and gives you a Firestore-based sending flow.
App that needs full custom email control
Use Cloud Functions with a transactional email provider.
This gives you the most flexibility, especially if you already have designers, templates or unusual email requirements.
App that needs normal transactional emails quickly
Use Cloud Functions with EmailsDone.
This keeps the safe Firebase backend pattern, but avoids turning email into a separate template and infrastructure project.
Which option should you choose?
Choose Firebase Auth emails if:
- you only need password reset and verification
- you want the fastest route
- the default Firebase templates are good enough
Choose Firebase Trigger Email if:
- you want a Firebase extension
- you want Firestore to act as your email queue
- you are happy configuring SMTP
- you want to manage the email content yourself
Choose a provider API if:
- you want a blank canvas
- you need custom HTML
- you already have templates
- you want full control over every message
Choose EmailsDone if:
- you want common app emails already available
- you do not want to write HTML
- you want Firebase/backend-friendly sending
- you want to avoid SMTP setup
- you want a small template-first API
- you would rather ship the app than build an email system
The practical Firebase pattern
Whichever route you choose for custom transactional email, do not put provider secrets in frontend code.
A safe Firebase architecture usually looks like this:
Frontend app
↓
Firebase Function
↓
Email service
↓
Recipient inbox
The Firebase Function should handle:
- authentication
- App Check
- validation
- secrets
- deciding whether the email should be sent
The email service handles the actual send.
With EmailsDone, the function can stay small:
import { onCall, HttpsError } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
import { EmailsDone } from "emailsdone";
const emailsDoneApiKey = defineSecret("EMAILSDONE_API_KEY");
export const sendWelcomeEmail = onCall(
{
secrets: [emailsDoneApiKey],
enforceAppCheck: true,
},
async (request) => {
if (!request.auth?.token.email) {
throw new HttpsError("unauthenticated", "You must be signed in.");
}
const emailsDone = EmailsDone.fromApiKey(emailsDoneApiKey.value());
await emailsDone
.authentication()
.welcome("https://app.example.com/get-started")
.send(request.auth.token.email);
return { queued: true };
}
);
The backend pattern is familiar Firebase.
The email template is the part you do not have to build.
Related Firebase email guides
If you are still choosing the right approach, start with the broader guide:
- How to send email from Firebase
If you want the secure backend pattern:
- Send email from Firebase Cloud Functions
If you are comparing the official extension:
- Firebase Trigger Email extension alternative
If you are working on account recovery:
- Firebase password reset emails
Final recommendation
Firebase gives you useful email pieces, but not one complete answer for every transactional email need.
Use the built-in Auth emails while they are enough.
Use Trigger Email if you want Firestore-to-SMTP plumbing.
Use a provider API if you want a blank canvas and full control.
Use EmailsDone if you want the normal app emails already done.
That is the real difference.
Some tools help you send anything.
EmailsDone helps you avoid building the boring emails in the first place.