2026-03-03 10:20:07 -06:00
# Email Signature Manager
A self-hosted, Dockerized Google Workspace email signature manager.
Runs as a single container — designed for Unraid but works on any Docker host.
## Features
- Pulls user data automatically from Google Workspace Directory API
- Renders per-user HTML signatures via Handlebars templates
- Pushes signatures directly to Gmail via `sendAs` API (web + mobile)
- Nightly batch push via configurable cron schedule
- Web admin UI — live template editor with real-time preview
- Single-user push for testing and onboarding
- SQLite audit log of every push event
- Basic auth protected UI
- Zero external services — single container, no database server
---
## Environment Variables
All secrets and configuration are passed as **environment variables at runtime ** .
No `.env` file is committed to this repo — see `.env.example` for all variable names and descriptions.
| Variable | Description | Default |
|---|---|---|
| `GOOGLE_ADMIN_EMAIL` | Workspace admin email for Directory API | * (required) * |
| `GOOGLE_CUSTOMER_ID` | Use `my_customer` for primary domain | `my_customer` |
| `SERVICE_ACCOUNT_KEY_PATH` | Path to sa.json inside container | `/app/secrets/sa.json` |
| `ADMIN_USERNAME` | Web UI login username | * (required) * |
| `ADMIN_PASSWORD` | Web UI login password | * (required) * |
| `PORT` | Internal app port | `3000` |
| `CRON_SCHEDULE` | Cron expression for nightly push | `0 2 * * *` |
| `NODE_ENV` | Node environment | `production` |
### On Unraid
Set each variable directly in the **Docker container template UI ** under the
"Variables" section. No file needed on the host.
### For Local Development
Copy `.env.example` to `.env` , fill in your values, then run:
```bash
docker-compose --env-file .env up -d --build
```
---
2026-03-03 10:22:00 -06:00
## Part 1 — Google Cloud Console Setup
This section walks through creating the service account and downloading the key
that allows the app to impersonate users in your Google Workspace domain.
### Step 1 — Create a Google Cloud Project
1. Open https://console.cloud.google.com and sign in with your Google Workspace admin account
2. Click the **project dropdown ** in the top navigation bar (next to the Google Cloud logo)
3. Click **New Project ** in the top right of the dialog
4. Enter a project name: `email-sig-manager`
5. Leave the organization as your Workspace domain → click **Create **
6. Wait a few seconds, then select the new project from the dropdown to make it active
> **Note:** If your org already has a GCP project you prefer to use, you can skip
> project creation and just enable the APIs inside the existing project.
---
### Step 2 — Enable Required APIs
The app needs two Google APIs enabled in your project.
**Enable Admin SDK API:**
1. In the GCP Console, go to **APIs & Services → Library ** (left sidebar)
2. Search for `Admin SDK API`
3. Click the result → click **Enable **
**Enable Gmail API:**
1. Go back to **APIs & Services → Library **
2. Search for `Gmail API`
3. Click the result → click **Enable **
Both should now show as **Enabled ** under **APIs & Services → Enabled APIs & Services ** .
---
### Step 3 — Create the Service Account
1. In the left sidebar, go to **IAM & Admin → Service Accounts **
2. Click * * + Create Service Account** at the top
3. Fill in the details:
- **Service account name:** `email-sig-manager`
- **Service account ID:** will auto-fill as `email-sig-manager`
- **Description:** `Email signature push service for Google Workspace`
4. Click **Create and Continue **
5. On the "Grant this service account access to project" step — **skip this, click Continue **
6. On the "Grant users access" step — **skip this too, click Done **
You will land back on the service accounts list and see your new account listed.
---
### Step 4 — Enable Domain-Wide Delegation on the Service Account
1. Click the service account name (`email-sig-manager` ) in the list to open it
2. Click the **Edit ** button (pencil icon) at the top
3. Scroll down to find * * "Show advanced settings"** — click it to expand
4. Check the box for * * "Enable Google Workspace Domain-wide Delegation"**
5. Enter a product name for the consent screen if prompted (e.g., `Email Signature Manager` )
6. Click **Save **
After saving, return to the service account detail page. You should now see a
**"Domain-wide delegation"** line showing a **Client ID ** (a long numeric string,
e.g., `112233445566778899` ). **Copy this Client ID ** — you will need it in Part 2.
---
### Step 5 — Download the Service Account JSON Key
1. On the service account detail page, click the **Keys ** tab
2. Click **Add Key → Create New Key **
3. Select **JSON ** as the key type → click **Create **
4. The key file downloads automatically to your computer
5. **Rename the file to `sa.json` **
6. Store it securely — this file grants impersonation access to all users in your domain
> This file goes in the `secrets/` folder on your Unraid host. It is gitignored
> and must never be committed to GitHub.
---
## Part 2 — Google Admin Console Authorization
This section delegates authority to the service account inside Google Admin,
allowing it to impersonate users and update their Gmail signatures.
### Step 6 — Authorize the Service Account in Google Admin
1. Open a new tab and go to https://admin.google.com
2. Sign in with your **Google Workspace super admin ** account
3. In the left sidebar, navigate to:
**Security → Access and data control → API controls **
4. On the API controls page, click **Manage Domain Wide Delegation **
5. Click **Add new ** to add a new authorized client
Fill in the form:
- **Client ID:** Paste the numeric Client ID you copied in Step 4
(find it again at GCP Console → IAM & Admin → Service Accounts → your account → Details)
- **OAuth Scopes:** Paste the following exactly, as one comma-separated line:
```
https://www.googleapis.com/auth/admin.directory.user.readonly,https://www.googleapis.com/auth/gmail.settings.basic
```
6. Click **Authorize **
> If you receive an error saying "client ID not found", wait 2– 3 minutes after
> enabling domain-wide delegation in Step 4 and try again — GCP propagation
> can take a moment.
2026-03-03 10:20:07 -06:00
---
2026-03-03 10:22:00 -06:00
### Step 7 — Verify the Authorization
1. After clicking Authorize you should see the new entry in the list
2. Click **View details ** next to your entry
3. Confirm both scopes are listed:
- `https://www.googleapis.com/auth/admin.directory.user.readonly`
- `https://www.googleapis.com/auth/gmail.settings.basic`
4. If a scope is missing, click **Edit ** , re-enter the full comma-separated scope string, and click **Authorize ** again
> **Multi-party approval note:** If your Google Workspace org has multi-party
> approval enabled for admin actions, another super admin will need to approve
> this delegation before it takes effect.
---
## Part 3 — Unraid Deployment
### Step 8 — Clone the Repository
SSH into your Unraid server or use the Unraid terminal:
2026-03-03 10:20:07 -06:00
```bash
cd /mnt/user/appdata
git clone https://github.com/YOURUSERNAME/email-sig-manager.git
cd email-sig-manager
```
2026-03-03 10:22:00 -06:00
### Step 9 — Place the Service Account Key
Copy `sa.json` (downloaded in Step 5) into the `secrets/` folder:
2026-03-03 10:20:07 -06:00
```bash
2026-03-03 10:22:00 -06:00
# From your PC to Unraid (run this on your PC):
scp sa.json root@UNRAID -IP:/mnt/user/appdata/email-sig-manager/secrets/sa.json
2026-03-03 10:20:07 -06:00
```
2026-03-03 10:22:00 -06:00
Or copy it via your Unraid SMB share using Windows Explorer:
2026-03-03 10:20:07 -06:00
```
2026-03-03 10:22:00 -06:00
\\UNRAID-IP\appdata\email-sig-manager\secrets\
2026-03-03 10:20:07 -06:00
```
2026-03-03 10:22:00 -06:00
### Step 10 — Add Your Company Logo
Place your logo file at:
```
/mnt/user/appdata/email-sig-manager/public/assets/logo.png
```
Recommended: PNG, transparent background, 160× 160px or smaller.
### Step 11 — Configure Container in Unraid Docker UI
1. In the Unraid web UI, go to **Docker → Add Container **
2. Set the following:
- **Name:** `email-sig-manager`
- **Repository:** * (leave blank if building locally — use the path method below) *
- **Network Type:** Bridge
**Add these Volume mappings:**
| Container Path | Host Path |
|---|---|
| `/app/secrets` | `/mnt/user/appdata/email-sig-manager/secrets` |
| `/app/data` | `/mnt/user/appdata/email-sig-manager/data` |
| `/app/public/assets` | `/mnt/user/appdata/email-sig-manager/public/assets` |
**Add these Port mapping:**
| Container Port | Host Port |
|---|---|
| `3000` | `3000` * (or any open port) * |
**Add these Variables** (click Add Variable for each):
| Name | Value |
|---|---|
| `GOOGLE_ADMIN_EMAIL` | `admin@messagepointmedia.com` |
| `GOOGLE_CUSTOMER_ID` | `my_customer` |
| `SERVICE_ACCOUNT_KEY_PATH` | `/app/secrets/sa.json` |
| `ADMIN_USERNAME` | `admin` |
| `ADMIN_PASSWORD` | * (your chosen password) * |
| `PORT` | `3000` |
| `CRON_SCHEDULE` | `0 2 * * *` |
| `NODE_ENV` | `production` |
### Step 12 — Build and Start
2026-03-03 10:20:07 -06:00
```bash
2026-03-03 10:22:00 -06:00
cd /mnt/user/appdata/email-sig-manager
2026-03-03 10:20:07 -06:00
docker-compose up -d --build
```
2026-03-03 10:22:00 -06:00
---
## Part 4 — First Run & Verification
### Step 13 — Access the Admin UI
Open a browser and navigate to:
2026-03-03 10:20:07 -06:00
```
http://UNRAID-IP:3000
```
2026-03-03 10:22:00 -06:00
Log in with your `ADMIN_USERNAME` and `ADMIN_PASSWORD` .
2026-03-03 10:20:07 -06:00
2026-03-03 10:22:00 -06:00
### Step 14 — Test with a Single User First
2026-03-03 10:20:07 -06:00
2026-03-03 10:22:00 -06:00
1. Go to the **Template Editor ** page
2. Verify the live preview renders correctly with your info
3. Make any template adjustments needed
4. Return to the **Dashboard **
5. In the **Push Single User ** field, enter your own email address
6. Click **Push Single User **
7. Open Gmail in a new tab → compose a new email → verify the signature appears correctly
8. Check the Gmail mobile app as well (force-close and reopen if needed)
2026-03-03 10:20:07 -06:00
2026-03-03 10:22:00 -06:00
### Step 15 — Push to All Users
Once you've verified your own signature looks correct:
1. Click **Push to All Users ** on the Dashboard
2. Confirm the dialog
3. Watch the audit log populate with success/error statuses
4. The nightly cron will take over automatically from here
2026-03-03 10:20:07 -06:00
---
2026-03-03 10:22:00 -06:00
## Updating the Project
2026-03-03 10:20:07 -06:00
```bash
cd /mnt/user/appdata/email-sig-manager
git pull
docker-compose up -d --build
```
2026-03-03 10:22:00 -06:00
`secrets/` , `data/` , and host-managed files are gitignored and unaffected by pulls.
2026-03-03 10:20:07 -06:00
---
## Project Structure
```
email-sig-manager/
├── src/
│ ├── index.js # Express app entry
│ ├── routes/
│ │ ├── admin.js # Template, logs, users API
│ │ └── push.js # Signature push logic + batch runner
│ ├── services/
│ │ ├── googleAdmin.js # Directory API — fetch all users
│ │ ├── gmailApi.js # Gmail sendAs patch
│ │ ├── renderer.js # Handlebars template renderer
│ │ └── scheduler.js # node-cron nightly job
│ └── db/
│ ├── sqlite.js # DB init and connection
│ └── audit.js # Audit log read/write
├── templates/
│ └── default.hbs # 2-column HTML signature template
├── public/
│ ├── dashboard.html # Admin dashboard UI
│ ├── editor.html # Template editor with live preview
│ └── assets/
│ └── logo.png # Company logo (add your own)
├── secrets/ # Gitignored — sa.json goes here
├── data/ # Gitignored — SQLite DB lives here
├── Dockerfile
├── docker-compose.yml
├── package.json
└── .env.example # Variable reference — safe to commit
```
---
## Cron Schedule Reference
| Expression | Meaning |
|---|---|
| `0 2 * * *` | 2:00 AM every day * (default) * |
| `0 6 * * *` | 6:00 AM every day |
| `0 2 * * 1` | 2:00 AM every Monday |
| `0 2 1 * *` | 2:00 AM on the 1st of each month |
---
## Troubleshooting
**Container won't start**
2026-03-03 10:22:00 -06:00
Run `docker logs email-sig-manager` . Most likely a missing env variable or malformed `sa.json` .
2026-03-03 10:20:07 -06:00
**"No primary sendAs" error**
2026-03-03 10:22:00 -06:00
The user may not have an active Gmail account, or domain-wide delegation scopes were not saved correctly in Google Admin.
**"Client ID not found" when authorizing in Google Admin**
Wait 2– 3 minutes after enabling domain-wide delegation in GCP and try again.
2026-03-03 10:20:07 -06:00
**Signatures not showing on mobile**
2026-03-03 10:22:00 -06:00
Gmail iOS/Android automatically uses the web signature. Have the user force-close and reopen the Gmail app after the push.
2026-03-03 10:20:07 -06:00
**Template changes not saving**
Verify the container has write access to the `templates/` directory. A `.bak` file is created on every save.
2026-03-03 10:22:00 -06:00
**401 / permission denied errors in logs**
The OAuth scopes in Google Admin may not have saved correctly. Go back to Admin Console → Security → API Controls → Domain Wide Delegation → View Details and verify both scopes are listed.
2026-03-03 10:20:07 -06:00
---
## Security Notes
- `sa.json` grants impersonation rights to every user in your domain — treat it like a master key
- Never commit `sa.json` or a populated `.env` to GitHub
- Change `ADMIN_PASSWORD` before first deployment
- Consider placing the UI behind HTTPS if accessible outside your LAN