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.
|
2026-03-13 13:39:18 -05:00
|
|
|
|
See [INSTALL.md](INSTALL.md) for Unraid-specific instructions.
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
2026-03-13 14:04:10 -05:00
|
|
|
|
- **Automated Directory Sync** — Pulls user data from Google Workspace Directory API
|
|
|
|
|
|
- **HTML Signature Renderer** — Per-user signatures via Handlebars templates
|
|
|
|
|
|
- **Gmail API Integration** — Pushes signatures directly to Gmail (web + mobile)
|
|
|
|
|
|
- **Nightly Sync** — Automated batch push via configurable cron schedule
|
|
|
|
|
|
- **Web Admin UI** — Management dashboard with live template editor
|
|
|
|
|
|
- **Template Versioning** — Save multiple versions and recall them later
|
|
|
|
|
|
- **Test Mode** — Single-user push for safe testing and onboarding
|
|
|
|
|
|
- **Audit Logging** — SQLite-backed history of every push event
|
|
|
|
|
|
- **Secure Access** — Basic auth protected management interface
|
|
|
|
|
|
- **Self-Hosted** — Single container with internal SQLite, no external DB needed
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
## Logo & Image Hosting
|
|
|
|
|
|
|
|
|
|
|
|
All images referenced in the signature template must be publicly accessible via HTTPS.
|
|
|
|
|
|
The default logo URL is:
|
|
|
|
|
|
```
|
|
|
|
|
|
https://alwisp.com/uploads/logo.png
|
|
|
|
|
|
```
|
|
|
|
|
|
Upload your logo and any other signature images to `https://alwisp.com/uploads/` and
|
|
|
|
|
|
reference them by full URL in the template. The logo URL can also be changed live
|
|
|
|
|
|
in the Template Editor UI without rebuilding the container.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-03 10:20:07 -06:00
|
|
|
|
## Environment Variables
|
|
|
|
|
|
|
|
|
|
|
|
All secrets and configuration are passed as **environment variables at runtime**.
|
2026-03-03 16:23:30 -06:00
|
|
|
|
No `.env` file is committed to this repo — see `.env.example` for all variable names.
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
| Variable | Description | Default |
|
|
|
|
|
|
|---|---|---|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
| `GOOGLE_ADMIN_EMAIL` | Workspace admin email (e.g. jason@messagepoint.tv) | *(required)* |
|
2026-03-03 10:20:07 -06:00
|
|
|
|
| `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
|
2026-03-03 16:23:30 -06:00
|
|
|
|
Set each variable directly in the **Docker container template UI** under Variables.
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
### For Local Development
|
|
|
|
|
|
```bash
|
2026-03-03 16:23:30 -06:00
|
|
|
|
cp .env.example .env
|
|
|
|
|
|
# fill in values
|
2026-03-03 10:20:07 -06:00
|
|
|
|
docker-compose --env-file .env up -d --build
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-03 10:22:00 -06:00
|
|
|
|
## Part 1 — Google Cloud Console Setup
|
|
|
|
|
|
|
|
|
|
|
|
### Step 1 — Create a Google Cloud Project
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
1. Open https://console.cloud.google.com — sign in with your Google Workspace admin account
|
|
|
|
|
|
2. Click the **project dropdown** (top nav bar) → **New Project**
|
|
|
|
|
|
3. Name: `email-sig-manager` → **Create**
|
|
|
|
|
|
4. Select the new project from the dropdown to make it active
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
|
|
|
|
|
### Step 2 — Enable Required APIs
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**Admin SDK API:**
|
|
|
|
|
|
1. Go to **APIs & Services → Library**
|
|
|
|
|
|
2. Search `Admin SDK API` → click result → **Enable**
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**Gmail API:**
|
2026-03-03 10:22:00 -06:00
|
|
|
|
1. Go back to **APIs & Services → Library**
|
2026-03-03 16:23:30 -06:00
|
|
|
|
2. Search `Gmail API` → click result → **Enable**
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
|
|
|
|
|
### Step 3 — Create the Service Account
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
1. Go to **IAM & Admin → Service Accounts**
|
|
|
|
|
|
2. Click **+ Create Service Account**
|
|
|
|
|
|
3. Name: `email-sig-manager` → **Create and Continue**
|
|
|
|
|
|
4. Skip role assignment → **Continue** → skip user access → **Done**
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 4 — Enable Domain-Wide Delegation
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
1. Click the service account name to open it
|
|
|
|
|
|
2. Click **Edit** (pencil icon)
|
|
|
|
|
|
3. Expand **Show advanced settings**
|
|
|
|
|
|
4. Check **Enable Google Workspace Domain-wide Delegation**
|
|
|
|
|
|
5. Enter product name: `Email Signature Manager`
|
2026-03-03 10:22:00 -06:00
|
|
|
|
6. Click **Save**
|
2026-03-03 16:23:30 -06:00
|
|
|
|
7. Back on the service account detail page, note the **Client ID** (long number) — copy it
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 5 — Download the JSON Key
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
1. Click the **Keys** tab on the service account
|
|
|
|
|
|
2. **Add Key → Create New Key → JSON → Create**
|
|
|
|
|
|
3. Rename the downloaded file to `sa.json`
|
|
|
|
|
|
4. Place it at `secrets/sa.json` on your Unraid host — never commit to GitHub
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Part 2 — Google Admin Console Authorization
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 6 — Authorize Domain-Wide Delegation
|
|
|
|
|
|
|
|
|
|
|
|
1. Go to https://admin.google.com — sign in as super admin
|
|
|
|
|
|
2. Navigate to: **Security → Access and data control → API controls**
|
|
|
|
|
|
3. Click **Manage Domain Wide Delegation**
|
|
|
|
|
|
4. Click **Add new**
|
|
|
|
|
|
5. Fill in:
|
|
|
|
|
|
- **Client ID:** *(paste the number from Step 4)*
|
|
|
|
|
|
- **OAuth Scopes:**
|
|
|
|
|
|
```
|
|
|
|
|
|
https://www.googleapis.com/auth/admin.directory.user.readonly,https://www.googleapis.com/auth/gmail.settings.basic
|
|
|
|
|
|
```
|
2026-03-03 10:22:00 -06:00
|
|
|
|
6. Click **Authorize**
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
> If you get "client ID not found" — wait 2–3 minutes after Step 4 and try again.
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 7 — Verify
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
1. Click **View details** next to your new entry
|
|
|
|
|
|
2. Confirm both scopes are listed
|
|
|
|
|
|
3. If a scope is missing, click Edit, re-enter the full scope string, and re-authorize
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Part 3 — Unraid Deployment
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 8 — Clone the Repo
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd /mnt/user/appdata
|
2026-03-13 14:03:18 -05:00
|
|
|
|
git clone https://git.alwisp.com/jason/email.git email-sigs
|
2026-03-03 16:23:30 -06:00
|
|
|
|
cd email-sigs
|
2026-03-03 10:20:07 -06:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-03 10:22:00 -06:00
|
|
|
|
### Step 9 — Place the Service Account Key
|
|
|
|
|
|
|
2026-03-03 10:20:07 -06:00
|
|
|
|
```bash
|
2026-03-03 16:23:30 -06:00
|
|
|
|
# From your PC (run on your PC):
|
|
|
|
|
|
scp sa.json root@UNRAID-IP:/mnt/user/appdata/email-sigs/secrets/sa.json
|
2026-03-03 10:20:07 -06:00
|
|
|
|
```
|
2026-03-03 16:23:30 -06:00
|
|
|
|
Or copy via SMB: `\\UNRAID-IP\appdata\email-sigs\secrets\`
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 10 — Upload Logo
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
Upload your logo to `https://alwisp.com/uploads/logo.png` via your web server.
|
|
|
|
|
|
The template references this URL directly — no local file needed.
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 11 — Build the Docker Image
|
|
|
|
|
|
|
|
|
|
|
|
SSH into Unraid and run:
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd /mnt/user/appdata/email-sigs
|
|
|
|
|
|
docker build -t email-sigs:latest .
|
2026-03-03 10:22:00 -06:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 12 — Configure Container in Unraid Docker UI
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
- **Name:** `email-sigs`
|
|
|
|
|
|
- **Repository:** `email-sigs` *(local image built in Step 11)*
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**Volume mappings:**
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
| Host Path | Container Path |
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|---|---|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
| `/mnt/user/appdata/email-sigs/secrets` | `/app/secrets` |
|
|
|
|
|
|
| `/mnt/user/appdata/email-sigs/data` | `/app/data` |
|
|
|
|
|
|
| `/mnt/user/appdata/email-sigs/public/assets` | `/app/public/assets` |
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**Port mapping:** Host `3000` → Container `3000`
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**Variables:**
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
|
|
|
|
|
| Name | Value |
|
|
|
|
|
|
|---|---|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
| `GOOGLE_ADMIN_EMAIL` | `jason@messagepoint.tv` |
|
2026-03-03 10:22:00 -06:00
|
|
|
|
| `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` |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Part 4 — First Run & Verification
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 13 — Access the UI
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 10:20:07 -06:00
|
|
|
|
```
|
|
|
|
|
|
http://UNRAID-IP:3000
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
### Step 14 — Preview and Test
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
1. Go to **Template Editor** — verify the preview renders correctly
|
|
|
|
|
|
2. Back on **Dashboard** → enter your own email → **Push Single User**
|
|
|
|
|
|
3. Open Gmail → compose a new message → verify signature appears
|
|
|
|
|
|
4. Check Gmail mobile app (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
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
Once verified, click **Push to All Users** on the Dashboard.
|
|
|
|
|
|
The nightly cron will keep all signatures in sync automatically.
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
## Updating
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-03-03 16:23:30 -06:00
|
|
|
|
cd /mnt/user/appdata/email-sigs
|
2026-03-03 10:20:07 -06:00
|
|
|
|
git pull
|
2026-03-03 16:23:30 -06:00
|
|
|
|
docker build -t email-sigs:latest .
|
|
|
|
|
|
docker restart email-sigs
|
2026-03-03 10:20:07 -06:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Project Structure
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
email-sig-manager/
|
|
|
|
|
|
├── src/
|
2026-03-03 16:23:30 -06:00
|
|
|
|
│ ├── index.js # Express app (no dotenv — vars injected by Docker)
|
2026-03-03 10:20:07 -06:00
|
|
|
|
│ │ ├── 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
|
2026-03-03 16:23:30 -06:00
|
|
|
|
│ └── assets/ # Local asset dir (optional, logo served remotely)
|
2026-03-03 10:20:07 -06:00
|
|
|
|
├── secrets/ # Gitignored — sa.json goes here
|
|
|
|
|
|
├── data/ # Gitignored — SQLite DB lives here
|
2026-03-03 16:23:30 -06:00
|
|
|
|
├── Dockerfile # Uses npm install (not npm ci)
|
2026-03-03 10:20:07 -06:00
|
|
|
|
├── 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
|
|
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**"Cannot find module" errors on startup**
|
|
|
|
|
|
Rebuild the image: `docker build -t email-sigs:latest .` then restart.
|
|
|
|
|
|
|
2026-03-03 10:20:07 -06:00
|
|
|
|
**Container won't start**
|
2026-03-03 16:23:30 -06:00
|
|
|
|
Run `docker logs email-sigs` — 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 16:23:30 -06:00
|
|
|
|
The user may not have an active Gmail account, or domain-wide delegation scopes were not saved correctly.
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**"Client ID not found" in Google Admin**
|
|
|
|
|
|
Wait 2–3 minutes after enabling domain-wide delegation in GCP, then try again.
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
**Signatures not showing on mobile**
|
2026-03-03 16:23:30 -06:00
|
|
|
|
Gmail iOS/Android uses the web signature automatically. Force-close and reopen the app.
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
|
|
|
|
|
**Template changes not saving**
|
2026-03-03 16:23:30 -06:00
|
|
|
|
Verify the container has write access to `templates/`. A `.bak` file is created on every save.
|
2026-03-03 10:20:07 -06:00
|
|
|
|
|
2026-03-03 16:23:30 -06:00
|
|
|
|
**401 / permission denied errors**
|
|
|
|
|
|
Go to Google Admin → Security → API Controls → Domain Wide Delegation → View Details
|
|
|
|
|
|
and verify both OAuth scopes are listed correctly.
|
2026-03-03 10:22:00 -06:00
|
|
|
|
|
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
|
2026-03-03 16:23:30 -06:00
|
|
|
|
- Never commit `sa.json` to GitHub — it is gitignored
|
2026-03-03 10:20:07 -06:00
|
|
|
|
- Change `ADMIN_PASSWORD` before first deployment
|
2026-03-03 16:23:30 -06:00
|
|
|
|
- Consider HTTPS via a reverse proxy if the UI is accessible outside your LAN
|