Firebase doesn't ship a list of email providers, but it sends through any SMTP server or HTTPS API, so it works with OwnMailServer in three ways. Use custom SMTP in Firebase Auth for password resets and verification emails, the Trigger Email from Firestore extension to send by writing documents, or a Cloud Function that calls our API for full control. Most apps use the first plus one of the other two.
| You want to send | Use | Code needed |
|---|---|---|
| Password resets, email verification, sign-in links | Firebase Auth custom SMTP | None |
| Emails written as Firestore documents | Trigger Email extension, or the function below | None / about 40 lines |
| Receipts, alerts, anything with your own template | A Cloud Function calling POST /emails
|
About 40 lines |
| Fully custom auth emails | A function with generatePasswordResetLink
|
About 30 lines |
What you need before you start
- An approved OwnMailServer account with a verified sending domain. Every From address must be on that domain, or we refuse the email with
550. The API docs list the DNS records, and the DKIM guide explains them. - An API key, created in your dashboard under API keys & SMTP. Choose Sending only (the default), limited to your sending domain: it can send and nothing else. It's the SMTP password and the API bearer token. Create one per use, so you can revoke one without breaking the others.
- For Cloud Functions or extensions: a Firebase project on the Blaze plan and the Firebase CLI.
Option 1: Firebase Auth emails over custom SMTP
Firebase Auth sends its own emails for password resets, email address verification, email link sign-in and email change. By default they come from a Firebase sender. Point them at us and they come from your domain, signed with your DKIM key, with bounces in your dashboard.
In the Firebase console, open Authentication, then Templates, then SMTP settings, turn it on and enter:
| Field | Value |
|---|---|
| Sender address | An address on your verified domain, such as no-reply@acme.com
|
| SMTP server host | smtp.ownmailserver.com |
| Port | 587 |
| Username |
ownmailserver (we accept any value) |
| Password | Your API key |
| Security mode | STARTTLS |
Save, then trigger a password reset for a test account. If nothing arrives, look the address up in your dashboard first. A 535 means the password isn't a valid API key, and a 550 means the sender domain isn't verified. If the message isn't there at all, turn SMTP off and try again: if Firebase's own sender then delivers, the settings are the problem.
Firebase still writes these emails and their links, so you edit the text in the same Templates screen. If you've upgraded to Identity Platform with multi-tenancy, Google's docs say tenants must be set to inherit custom SMTP settings, or their users keep getting the default sender.
Option 2: The Trigger Email from Firestore extension
The Trigger Email extension (firebase/firestore-send-email) watches a collection. Each new document becomes an email, and the extension writes the delivery state back onto the document.
Install it from the console or the CLI:
firebase ext:install firebase/firestore-send-email
When it asks for settings:
| Parameter | Value |
|---|---|
| Authentication Type | Username & Password |
| SMTP connection URI | smtp://ownmailserver@smtp.ownmailserver.com:587 |
| SMTP password | Your API key (stored in Secret Manager) |
| Email documents collection | mail |
| Default FROM address | Acme <no-reply@acme.com> |
Use smtp:// with port 587, not smtps://: the connection starts plain and upgrades with STARTTLS, which we require before login. Then send an email by adding a document:
import { getFirestore, collection, addDoc } from "firebase/firestore";
await addDoc(collection(getFirestore(), "mail"), {
to: ["jane@example.com"],
message: {
subject: "Your receipt",
text: "Thanks for your order.",
html: "<p>Thanks for your order.</p>",
},
});
Two cautions. If clients can write to mail, anyone signed in to your app can email anyone, so write these documents from trusted server code or lock the collection down with security rules. And Firebase says the Extensions service shuts down on March 31, 2027: installed extensions keep running, but you can't reconfigure or update them after that. For a new project, use Option 3, which reads the same document shape.
Option 3: A Cloud Function that calls our API
A second-generation Cloud Function can call POST /emails with plain fetch, which Node.js 18 and later include. This one replaces the extension: it sends every new mail document and records the result on it.
Store the API key as a secret first:
firebase functions:secrets:set OMS_API_KEY
// functions/index.js
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { defineSecret } from "firebase-functions/params";
const OMS_API_KEY = defineSecret("OMS_API_KEY");
export const sendMail = onDocumentCreated(
{ document: "mail/{id}", secrets: [OMS_API_KEY] },
async (event) => {
const doc = event.data.data();
const res = await fetch("https://api.ownmailserver.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${OMS_API_KEY.value()}`,
"Content-Type": "application/json",
// Firestore triggers can run more than once; the key stops a second send.
"Idempotency-Key": `mail-${event.params.id}`,
},
body: JSON.stringify({
from: doc.from ?? "Acme <no-reply@acme.com>",
to: doc.to,
subject: doc.message.subject,
html: doc.message.html,
text: doc.message.text,
}),
});
const body = await res.json();
if (!res.ok) {
await event.data.ref.update({ delivery: { state: "ERROR", error: `${body.name}: ${body.message}` } });
// 429 and 5xx are worth retrying; turn on retries in the function's settings.
if (res.status === 429 || res.status >= 500) throw new Error(body.message);
return;
}
await event.data.ref.update({ delivery: { state: "SENT", id: body.id } });
},
);
firebase deploy --only functions
The Idempotency-Key header matters here. Firestore triggers deliver at least once, so a function can run twice for one document. With the same key within 24 hours, our API returns the first response and sends nothing again.
From here, the rest of the API works the same way: scheduled sends, batches of up to 100, tags and webhooks for deliveries and bounces. Our API also follows Resend's request shapes, so if you already call Resend from a function, you can keep the code and change the base URL.
Option 4: Custom auth emails with your own template
If you want auth emails in your own design, let the Admin SDK create the link and send it yourself:
// Same functions/index.js as above, so OMS_API_KEY is already defined.
import { onCall } from "firebase-functions/v2/https";
import { initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";
initializeApp();
export const requestPasswordReset = onCall({ secrets: [OMS_API_KEY] }, async (req) => {
const email = String(req.data.email ?? "");
let link;
try {
link = await getAuth().generatePasswordResetLink(email, { url: "https://acme.com/login" });
} catch {
return { ok: true }; // Don't reveal whether the account exists.
}
await fetch("https://api.ownmailserver.com/emails", {
method: "POST",
headers: { Authorization: `Bearer ${OMS_API_KEY.value()}`, "Content-Type": "application/json" },
body: JSON.stringify({
from: "Acme <no-reply@acme.com>",
to: [email],
subject: "Reset your Acme password",
html: `<p>Click to choose a new password:</p><p><a href="${link}">Reset password</a></p>`,
}),
});
return { ok: true };
});
generateEmailVerificationLink and generateSignInWithEmailLink work the same way. Call this function from your app instead of the client SDK's sendPasswordResetEmail. It's a public endpoint, so protect it with App Check or a rate limit, or it becomes a way to flood inboxes from your domain.
Limits to plan for
-
10 requests or messages a second per account. A burst from a batch job gets
429from the API or450over SMTP; retry with backoff, or usePOST /emails/batch. - Warm-up cap. A new account's daily limit starts at a few hundred recipients and rises after each clean day (how warm-up works). Password resets count, so move your highest-volume email last, and keep your old provider for it until the cap is high enough.
- 50 recipients and 10 MB per message.
- Suppressions. We drop email to addresses that hard-bounced or complained, and don't count it toward your cap. Fix typos in sign-up forms early, since every bad address bounces.
When this isn't the right fit
If you send a few hundred auth emails a month, Firebase's default sender is free and you may not need any of this. The reasons to switch are your own domain on every email, bounce and complaint data you can see, and one provider for auth and app email. At our pay-as-you-go pricing, the $10 monthly minimum covers the first 50,000 emails.
FAQ
Does Firebase integrate with OwnMailServer?
Yes, through standard SMTP and HTTPS rather than a listed integration. Firebase Auth can send password reset and verification emails through any custom SMTP server, the Trigger Email from Firestore extension accepts any SMTP connection URI, and Cloud Functions can call our REST API with fetch. All three take a few minutes once your sending domain is verified.
Can Firebase Auth send password reset emails through my own SMTP server?
Yes. In the Firebase console, open Authentication, then Templates, then SMTP settings. Turn it on and enter a sender address on your verified domain, smtp.ownmailserver.com, port 587, STARTTLS, any username and an API key as the password. Firebase keeps its own templates and links; we deliver the email and record bounces. Confirm the menu names in your console, since Google moves them occasionally.
Is the Trigger Email from Firestore extension going away?
Firebase says the Extensions service is deprecated and shuts down on March 31, 2027. Installed extensions keep running, but after that date you can't update, reconfigure or uninstall them. If you use Trigger Email today it keeps working, but for a new project a short Cloud Function that calls our API is the safer choice, and this guide includes one.
Do I need the Firebase Blaze plan?
For Cloud Functions and extensions, yes: both need the pay-as-you-go Blaze plan, and functions can't reach outside services without it. Firebase Auth's SMTP setting doesn't depend on Cloud Functions. Firebase's own usage charges are separate from ours, which are $0.20 per 1,000 emails with a $10 monthly minimum.
Can I put the API key in my Firebase web or mobile app?
No. Even a sending-only key can send email as your domain, and a full-access key can also manage your account, so it belongs on the server only. Keep it in Secret Manager through defineSecret, and have the app write a Firestore document or call a callable function instead. Lock that path down with security rules or App Check so strangers can't use your app to send email.
Originally published at ownmailserver.com.
Top comments (0)