Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions Node/quickstarts/email-users/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
firebase-debug.log*

# Firebase cache
.firebase/

# Firebase config

# Uncomment this if you'd like others to create their own Firebase project.
# For a team working on the same Firebase project(s), it is recommended to leave
# it commented so all members can deploy to the same project(s) in .firebaserc.
# .firebaserc

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
82 changes: 82 additions & 0 deletions Node/quickstarts/email-users/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Firebase SDK for Cloud Functions Quickstart - Auth triggers

This quickstart demonstrates how to setup an Auth triggered Cloud Function using the **Firebase SDK for Cloud Functions** and [Resend](https://resend.com/).

## Introduction

We'll deploy 2nd gen Auth triggered functions that send a welcome email when a new user signs up and a goodbye email when user accounts are deleted.

- [Read more about Cloud Functions for Firebase](https://firebase.google.com/docs/functions/)
- [Read more about the Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite)
- [Connect Cloud Functions to the Emulator](https://firebase.google.com/docs/emulator-suite/connect_functions)

## Functions Code

The functions are organized into separate files for clean, self-contained documentation snippets and modular code:

- [functions/sendWelcomeEmail.js](functions/sendWelcomeEmail.js): `sendWelcomeEmail` triggers on all user creations across the project (default behavior).
- [functions/sendByeEmail.js](functions/sendByeEmail.js): `sendByeEmail` triggers when any user account is deleted.
- [functions/tenants.js](functions/tenants.js): Multi-tenancy examples:
- `sendWelcomeEmailToTenant`: Scoped to users in a specific Identity Platform tenant using `tenantId: "my-tenant-id"`.
- `sendWelcomeEmailNoTenant`: Triggers only for users not associated with any tenant using `tenantId: IS_NOT_TENANT`.
- [functions/utils/myEmailService.js](functions/utils/myEmailService.js): Configures the Resend client, defines the `EMAIL_API_KEY` secret, and provides the `sendEmail`, `sendWelcomeEmail`, and `sendGoodbyeEmail` helpers.
- [functions/index.js](functions/index.js): Entry point re-exporting all function triggers.

Sending emails is performed using [Resend](https://resend.com/). The dependencies are listed in [functions/package.json](functions/package.json).

## Set up the sample

1. Clone or download this repo and navigate to `Node/quickstarts/email-users`:
```bash
cd Node/quickstarts/email-users
```
2. Install Cloud Functions dependencies:
```bash
cd functions && pnpm install && cd ..
```
3. Set your Resend API key for local development in `functions/.env.local`:
```bash
EMAIL_API_KEY="re_123456789"
```
> You can obtain a free API key from [Resend](https://resend.com/api-keys).

## Run locally with the Firebase Emulator Suite

The [Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) lets you test Auth triggers and create/delete users directly in the Emulator UI without deploying to a live project.

1. Start the emulators:
```bash
firebase emulators:start
```
2. Open the **Emulator Suite UI** in your browser at [http://localhost:4000](http://localhost:4000) (or the port printed in your terminal).
3. Navigate to the **Authentication** tab.
4. **Trigger `sendWelcomeEmail`**: Click **Add user**, enter an email address and display name, and click **Save**.
5. **Trigger `sendByeEmail`**: Select the user you just created and click **Delete user**.
6. View the logs in the **Logs** tab of the Emulator Suite UI or in your terminal to see the functions execute:
> `New welcome email sent to: user@example.com`
> `Account deletion confirmation email sent to: user@example.com`

## Deploy to production

To deploy the functions to a live Firebase project:

1. Configure your Firebase project:
```bash
firebase use --add
```
2. Set your Resend API key as a Cloud Secret:
```bash
firebase functions:secrets:set EMAIL_API_KEY
```
3. Deploy the functions:
```bash
firebase deploy --only functions
```

## Contributing

We'd love that you contribute to the project. Before doing so please read our [Contributor guide](../../CONTRIBUTING.md).

## License

© Google, 2026. Licensed under an [Apache-2](../../LICENSE) license.
17 changes: 17 additions & 0 deletions Node/quickstarts/email-users/firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"functions": {
"codebase": "email-users",
"predeploy": ["npm --prefix \"$RESOURCE_DIR\" run lint"]
},
"emulators": {
"auth": {
"port": 9099
},
"functions": {
"port": 5001
},
"ui": {
"enabled": true
}
}
}
42 changes: 42 additions & 0 deletions Node/quickstarts/email-users/functions/deletedUserFarewell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";

// [START sendByeEmail]
// [START onDeleteImport]
const { onUserDeleted } = require("firebase-functions/identity");
// [END onDeleteImport]
const { defineSecret } = require("firebase-functions/params");
const { logger } = require("firebase-functions");
const { sendGoodbyeEmail } = require("./utils/myEmailService");

const emailApiKey = defineSecret("EMAIL_API_KEY");

// [START onDeleteTrigger]
exports.deletedUserFarewell = onUserDeleted(
{ secrets: [emailApiKey] },
async (event) => {
// [END onDeleteTrigger]
const { uid, email, displayName } = event.data;
if (!email) {
logger.log(`User ${uid} does not have an email address.`);
return;
}

await sendGoodbyeEmail(email, displayName);
},
);
// [END sendByeEmail]
32 changes: 32 additions & 0 deletions Node/quickstarts/email-users/functions/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

module.exports = [
{
files: ["**/*.js", "**/*.cjs", "**/*.mjs"],
rules: {
"no-console": "off",
"no-unused-vars": "off",
"no-undef": "off",
"no-empty": "off",
"no-useless-escape": "off",
"no-prototype-builtins": "off",
"no-redeclare": "off",
"no-constant-condition": "off",
"no-case-declarations": "off",
},
},
];
28 changes: 28 additions & 0 deletions Node/quickstarts/email-users/functions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";

const { newUserWelcome } = require("./newUserWelcome");
const { deletedUserFarewell } = require("./deletedUserFarewell");
const {
sendWelcomeEmailToTenant,
sendWelcomeEmailNoTenant,
} = require("./tenants");

exports.newUserWelcome = newUserWelcome;
exports.deletedUserFarewell = deletedUserFarewell;
exports.sendWelcomeEmailToTenant = sendWelcomeEmailToTenant;
exports.sendWelcomeEmailNoTenant = sendWelcomeEmailNoTenant;
45 changes: 45 additions & 0 deletions Node/quickstarts/email-users/functions/newUserWelcome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
"use strict";

// [START sendWelcomeEmail]
// [START onCreateImport]
const { onUserCreated } = require("firebase-functions/identity");
// [END onCreateImport]
const { defineSecret } = require("firebase-functions/params");
const { logger } = require("firebase-functions");
const { sendWelcomeEmail } = require("./utils/myEmailService");

const emailApiKey = defineSecret("EMAIL_API_KEY");

// [START onCreateTrigger]
exports.newUserWelcome = onUserCreated(
{ secrets: [emailApiKey] },
async (event) => {
// [END onCreateTrigger]
// [START eventAttributes]
const { uid, email, displayName } = event.data;
Comment thread
jhuleatt marked this conversation as resolved.
// [END eventAttributes]

if (!email) {
logger.log(`User ${uid} does not have an email address.`);
return;
}

await sendWelcomeEmail(email, displayName);
},
);
// [END sendWelcomeEmail]
26 changes: 26 additions & 0 deletions Node/quickstarts/email-users/functions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint .",
"serve": "firebase emulators:start --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log",
"compile": "cp ../../../../tsconfig.template.json ./tsconfig-compile.json && tsc --project tsconfig-compile.json"
Comment thread
jhuleatt marked this conversation as resolved.
},
"engines": {
"node": "24"
},
"dependencies": {
"firebase-admin": "^14.2.0",
"firebase-functions": "7.3.3-rc.3",
"resend": "^4.0.0"
},
"devDependencies": {
"eslint": "^8.57.1",
"eslint-config-google": "^0.14.0"
},
"private": true
}
Loading
Loading