Firebase email with Groundmail

Send Firebase email from your own domain through Groundmail. There are three ways, and you can use more than one:

What you're sending Use Needs code
Firebase Auth emails (verification, password reset, email change) Firebase Auth SMTP settings No
Email when a Firestore document is written The Trigger Email extension A little
Any email from your backend A Cloud Function with the Node SDK Yes

Prerequisites

  • A Groundmail account with a verified sending domain. A subdomain such as mail.yourdomain.com works well.
  • For the SMTP paths, an SMTP credential: in the Groundmail dashboard, open SMTP and choose Create SMTP credential. The password (gmsmtp_…) is shown once, so copy it. See SMTP relay.
  • For Cloud Functions, an API key (gm_…) from API keys in the dashboard.
  • The Trigger Email extension and Cloud Functions need Firebase's Blaze (pay as you go) plan. The Auth SMTP settings don't.

1. Firebase Auth emails: SMTP settings

By default Firebase sends Auth emails from noreply@<project-id>.firebaseapp.com. To send them from your domain, open the Firebase console, go to Authentication → Templates → SMTP settings, turn SMTP on and enter:

Firebase field Value
Sender address An address on your verified domain, for example no-reply@mail.yourdomain.com
SMTP server host smtp.groundmail.dev
SMTP server port 587
SMTP account username groundmail
SMTP account password Your SMTP credential (gmsmtp_…)
SMTP security mode STARTTLS

Save. Firebase now sends verification, password reset and email change messages through Groundmail, using the templates you set in the same Templates tab.


2. Firestore: the Trigger Email extension

The Trigger Email extension (firebase/firestore-send-email) sends an email whenever a document is added to a collection. Install it from the Firebase console or the CLI:

firebase ext:install firebase/firestore-send-email

When it asks for configuration:

Extension setting Value
SMTP connection URI smtp://groundmail@smtp.groundmail.dev:587
SMTP password Your SMTP credential (gmsmtp_…), stored in Secret Manager
Email documents collection mail (or your own name)
Default FROM address An address on your verified domain

Then send by adding a document:

import { addDoc, collection } from "firebase/firestore";

await addDoc(collection(db, "mail"), {
  to: "ada@example.com",
  message: {
    subject: "Welcome to Acme",
    html: "<p>Thanks for signing up.</p>",
  },
});

The extension writes the result back to the document's delivery field.


3. Cloud Functions: the Node SDK

For full control over templates and sending, call the API from a function with the groundmail package:

cd functions
npm install groundmail
firebase functions:secrets:set GROUNDMAIL_API_KEY
// functions/src/index.ts
import { onCall } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
import { Groundmail } from "groundmail";

const GROUNDMAIL_API_KEY = defineSecret("GROUNDMAIL_API_KEY");

export const sendWelcome = onCall({ secrets: [GROUNDMAIL_API_KEY] }, async (req) => {
  const groundmail = new Groundmail(GROUNDMAIL_API_KEY.value());
  await groundmail.emails.send({
    from: "Acme <no-reply@mail.acme.com>",
    to: req.auth!.token.email!,
    subject: "Welcome to Acme",
    html: "<p>Thanks for signing up.</p>",
  });
});

To send Firebase password reset links from your own template, generate the link with the Firebase Admin SDK (getAuth().generatePasswordResetLink(email)) and put it in the email you send with emails.send.


4. Send a test

Send to a Groundmail simulator address so nothing reaches a real inbox, for example by adding a mail document with to: "delivered@simulator.groundmail.dev". Then open Emails in the Groundmail dashboard, or run:

curl "https://api.groundmail.dev/emails?limit=5" \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY"

The email appears with "test": true and the status delivered. All simulator outcomes are in Testing.


Troubleshooting

Firebase or the extension can't connect. The username is exactly groundmail, and the password is the SMTP credential (gmsmtp_…), not an API key (gm_…). Use port 587 with STARTTLS.

The message is refused because of the sender. The sender or FROM address must be on a verified domain on your account.

A recipient never receives the email. The address may be on your suppression list, usually after a hard bounce. See Suppressions.

You hit a sending limit. New accounts ramp up their daily volume, and the Free plan has a daily limit. See Sending limits.