feat(projects): migrate haumdaucherde website into monorepo and document in AGENTS.md
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"scriptId": "1pTpLk3kblp9xByrZ0rM-5gBU-5pZkbdKR25bzI89apFdU1RHPzBvzIl5",
|
||||
"rootDir": "src",
|
||||
"htmlExtensions": [
|
||||
".html"
|
||||
],
|
||||
"jsonExtensions": [
|
||||
".json"
|
||||
],
|
||||
"filePushOrder": [],
|
||||
"skipSubdirectories": false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# AGENTS.md
|
||||
|
||||
This document provides context for AI agents operating on the `mail_forwarding` infrastructure in this repository.
|
||||
|
||||
## Architecture
|
||||
This directory contains Google Apps Script code configured as Infrastructure as Code (IaC).
|
||||
It automates the creation of email forwarding in Google Workspace by reading from a Google Sheet and dynamically creating/managing Workspace Groups.
|
||||
|
||||
## Tooling
|
||||
- We use `@google/clasp` to manage the deployment of the `.js` files to Google Apps Script. Note: do NOT write TypeScript as we bypass the local transpilation step.
|
||||
- The entrypoint is `src/Code.js`.
|
||||
- The manifest is `src/appsscript.json`.
|
||||
|
||||
## Status
|
||||
- **Current Mode:** Active Production (`DRY_RUN: false`).
|
||||
- **Target:** All groups managed by this script are tagged with `[Auto-Forwarder] Managed by Google Sheets`.
|
||||
|
||||
## Rules & Safeguards (CRITICAL)
|
||||
1. **Never alter the `GROUP_DESCRIPTION_TAG` logic.**
|
||||
The script achieves declarative state management by finding and deleting Workspace Groups that are NOT present in the Google Sheet. To prevent the catastrophic deletion of real, human-managed groups (e.g., `board@haumdaucher.de`), the script relies on the `[Auto-Forwarder] Managed by Google Sheets` string in the group's description. The script must ALWAYS filter for this exact string before issuing any deletion API calls.
|
||||
2. **Always deploy via `clasp`.** Do not instruct the user to copy-paste code manually if `clasp` is available.
|
||||
3. **Trigger:** We use a combination of `onFormSubmit` (for Google Forms) and `onChange` (for manual sheet edits) triggers. This ensures reconciliation happens regardless of how the data was updated.
|
||||
4. **Validation Logic**: `forwardAddress` must always be internal (`@haumdaucher.de`), while `forwardToAddress` must always be external (non-haumdaucher) to prevent routing loops.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Mail Forwarding Automation
|
||||
|
||||
This directory contains an Infrastructure-as-Code (IaC) deployment for Google Apps Script.
|
||||
It automates the creation and synchronization of Google Workspace mail forwarding by reading from a Google Sheet (typically populated by Google Forms) and managing Workspace Groups.
|
||||
|
||||
## Prerequisites
|
||||
To deploy this code to your Google Workspace, you need the following installed on your machine:
|
||||
- [Node.js & npm](https://nodejs.org/)
|
||||
- Google Clasp CLI: Run `npm install -g @google/clasp`
|
||||
|
||||
## Step 1: Authentication & Setup
|
||||
Before you can deploy, you must authenticate your local machine with your Google Workspace Admin account and enable the Apps Script API.
|
||||
|
||||
1. **Enable the API:** Go to [https://script.google.com/home/usersettings](https://script.google.com/home/usersettings) and turn **ON** the "Google Apps Script API".
|
||||
2. **Login:** In your terminal, run:
|
||||
```bash
|
||||
clasp login
|
||||
```
|
||||
This will open a browser window. Sign in with your Workspace Admin account (`@haumdaucher.de`) and grant the necessary permissions.
|
||||
|
||||
## Step 2: Configuration
|
||||
You must configure the script to point to your specific Google Sheet.
|
||||
Open `src/Code.js` and modify the `CONFIG` block at the top of the file:
|
||||
|
||||
```javascript
|
||||
const CONFIG = {
|
||||
// 1. The ID of the Google Sheet (found in the URL: https://docs.google.com/spreadsheets/d/<THIS_ID>/edit)
|
||||
SPREADSHEET_ID: "YOUR_SHEET_ID_HERE",
|
||||
|
||||
// 2. The name of the tab at the bottom of the screen
|
||||
SHEET_NAME: "Form Responses 1",
|
||||
|
||||
// 3. The column numbers containing the data (1 = A, 2 = B, 3 = C, etc.)
|
||||
COL_FORWARD_ADDRESS: 6,
|
||||
COL_FORWARD_TO_ADDRESS: 4,
|
||||
|
||||
// 4. Your admin email for receiving reports
|
||||
ADMIN_EMAIL: "admin@haumdaucher.de",
|
||||
|
||||
// 5. Dry run mode. If true, script logs intended changes without modifying Workspace.
|
||||
DRY_RUN: true,
|
||||
|
||||
// ... leave the GROUP_DESCRIPTION_TAG untouched!
|
||||
};
|
||||
```
|
||||
|
||||
## Step 3: Deployment
|
||||
Once configured, you need to create an Apps Script project in your Google Account and push this code to it.
|
||||
|
||||
1. Navigate to this `mail_forwarding` directory in your terminal.
|
||||
2. Initialize the project as a standalone script:
|
||||
```bash
|
||||
clasp create --type standalone --title "Haumdaucher Mail Forwarding" --rootDir ./src
|
||||
```
|
||||
*(This creates a hidden `.clasp.json` file linking this directory to the cloud project).*
|
||||
3. Push the code:
|
||||
```bash
|
||||
clasp push
|
||||
```
|
||||
|
||||
> **⚠️ Important:** `clasp push` only uploads the code. It does **not** install or update the background triggers. You must always re-run the `setup` function in the Apps Script IDE after a push to ensure the triggers are active and up to date.
|
||||
|
||||
## Step 4: Initialization
|
||||
The code is now in the cloud, but the background triggers need to be activated and the Admin SDK authorized.
|
||||
|
||||
1. Open the project in your browser:
|
||||
```bash
|
||||
clasp open-script
|
||||
```
|
||||
2. **Ignore the large blue "Deploy" button.** You do *not* need to create a deployment. This script runs via background triggers, not as a web app.
|
||||
3. In the toolbar directly above the code editor, look for a dropdown menu showing function names (it might currently say `syncForwardings`). Click the dropdown and select `setup`.
|
||||
4. Click the **Run** button (the play icon) right next to the dropdown.
|
||||
5. **Authorization Required:** Google will prompt you to review permissions. *(Note: This interactive browser consent screen is a strict Google Workspace security requirement for scripts accessing the Admin API, which is why this specific step cannot be automated via CLI).*
|
||||
- Click "Review Permissions"
|
||||
- Choose your Admin account.
|
||||
- Click "Advanced" -> "Go to Haumdaucher Mail Forwarding (unsafe)"
|
||||
- Click "Allow" to grant access to the Admin Directory API, Gmail API, and Google Sheets.
|
||||
5. Once authorized, the `setup` function will finish executing. It installs the background `onChange` trigger.
|
||||
|
||||
## System Status: LIVE
|
||||
The system is currently configured for active production (`DRY_RUN: false`). Any changes to the Google Sheet will result in real-time creation, modification, or deletion of Google Workspace Groups.
|
||||
|
||||
To revert to dry-run mode for testing:
|
||||
1. Open `src/Code.js` and change `DRY_RUN: false` to `DRY_RUN: true`.
|
||||
2. Run `clasp push`.
|
||||
|
||||
**You are done!** Whenever a new response is submitted to the configured Google Sheet via Forms, the script will automatically run in the background, reconcile the forwarding groups, and send you an email report.
|
||||
|
||||
## Troubleshooting & Analysis
|
||||
If you find that the script is not triggering when a new form is submitted:
|
||||
|
||||
1. **Check Executions:** Run `clasp open-script` and click on the **Executions** tab (the list/bullet icon) in the left sidebar.
|
||||
- **No entries?** The trigger never fired. You may need to run the `setup()` function again to ensure the triggers are installed.
|
||||
- **Failed entries?** Click on the failed execution to see the exact error message and stack trace.
|
||||
2. **Re-Run Setup:** If you have pushed new code or changed your Google Account permissions, always re-run the `setup` function in the Apps Script UI to refresh the background triggers and authorization.
|
||||
3. **Trigger Source:** Ensure the Google Sheet you are using is the same one linked to your Google Form. The `onFormSubmit` trigger only fires on the specific sheet receiving the form responses.
|
||||
+3509
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "haumdaucher-mail-forwarding",
|
||||
"version": "1.0.0",
|
||||
"description": "Google Apps Script for automating mail forwarding via Workspace Groups",
|
||||
"scripts": {
|
||||
"login": "clasp login",
|
||||
"push": "clasp push"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@google/clasp": "^2.4.2",
|
||||
"@types/google-apps-script": "^1.0.83"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* CONFIGURATION BLOCK
|
||||
* -------------------
|
||||
* Update these values before running `npm run push` (clasp push) to deploy
|
||||
* the script to your Google Workspace environment.
|
||||
*/
|
||||
const CONFIG = {
|
||||
// If the script is bound to a sheet (using `clasp clone <id>`), you can leave this empty.
|
||||
// If deployed as a standalone script, YOU MUST provide the exact Spreadsheet ID.
|
||||
SPREADSHEET_ID: "1q4r08nBA_ClWv3ypPCQ6GVCfMVkQwSKzDSRiokkQQ8Q",
|
||||
|
||||
// The exact name of the tab containing the data (e.g., from Google Forms)
|
||||
SHEET_NAME: "Form Responses 1",
|
||||
|
||||
// Column indices (1-indexed).
|
||||
// Example: If Forward Address is Column D, index is 4. If Forward To is Column F, index is 6.
|
||||
COL_FORWARD_ADDRESS: 6,
|
||||
COL_FORWARD_TO_ADDRESS: 4,
|
||||
|
||||
// The admin email that should receive the execution reports
|
||||
ADMIN_EMAIL: "moritz@haumdaucher.de",
|
||||
|
||||
// The primary Workspace domain for validation and auto-appending
|
||||
WORKSPACE_DOMAIN: "haumdaucher.de",
|
||||
|
||||
// Dry run mode. If true, the script will only log what it would do and send the email,
|
||||
// but will NOT actually create, update, or delete any groups in Workspace.
|
||||
DRY_RUN: false,
|
||||
|
||||
// DO NOT CHANGE THIS VALUE.
|
||||
// This tag is added to the description of groups created by this script.
|
||||
// It acts as a safety guard to ensure the script NEVER deletes manually created groups.
|
||||
GROUP_DESCRIPTION_TAG: "[Auto-Forwarder] Managed by Google Sheets"
|
||||
};
|
||||
|
||||
/**
|
||||
* INSTALLATION:
|
||||
* Run this function ONCE manually from the Apps Script IDE after pushing.
|
||||
* It authorizes the script and installs the background trigger.
|
||||
*/
|
||||
function setup() {
|
||||
const ss = CONFIG.SPREADSHEET_ID
|
||||
? SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID)
|
||||
: SpreadsheetApp.getActiveSpreadsheet();
|
||||
|
||||
if (!ss) {
|
||||
throw new Error("Could not find spreadsheet. Ensure you are bound to a sheet or have set CONFIG.SPREADSHEET_ID.");
|
||||
}
|
||||
|
||||
// Clean up any existing triggers to prevent duplicates
|
||||
const triggers = ScriptApp.getProjectTriggers();
|
||||
for (let i = 0; i < triggers.length; i++) {
|
||||
if (triggers[i].getHandlerFunction() === 'syncForwardings') {
|
||||
ScriptApp.deleteTrigger(triggers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Install the onFormSubmit trigger (specifically for Google Forms submissions)
|
||||
ScriptApp.newTrigger('syncForwardings')
|
||||
.forSpreadsheet(ss)
|
||||
.onFormSubmit()
|
||||
.create();
|
||||
|
||||
// Also install an onChange trigger as a fallback (for manual sheet edits)
|
||||
ScriptApp.newTrigger('syncForwardings')
|
||||
.forSpreadsheet(ss)
|
||||
.onChange()
|
||||
.create();
|
||||
|
||||
// Verify and log the installed triggers so we can confirm the setup is correct
|
||||
const installedTriggers = ScriptApp.getProjectTriggers();
|
||||
console.log(`Setup complete. ${installedTriggers.length} trigger(s) are now active:`);
|
||||
for (const trigger of installedTriggers) {
|
||||
console.log(` - Function: ${trigger.getHandlerFunction()}, Event: ${trigger.getEventType()}, Source: ${trigger.getTriggerSource()}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MAIN ENTRY POINT
|
||||
* This is triggered automatically whenever the spreadsheet changes (e.g., new form submission).
|
||||
*/
|
||||
function syncForwardings() {
|
||||
try {
|
||||
console.log("Starting syncForwardings...");
|
||||
if (CONFIG.DRY_RUN) {
|
||||
console.log("dry_run enabled, no changes will be applied.");
|
||||
} else {
|
||||
console.log("dry_run is false, actively performing changes.");
|
||||
}
|
||||
|
||||
const desiredState = readDesiredStateFromSheet();
|
||||
const currentState = readCurrentStateFromWorkspace();
|
||||
|
||||
const changelog = [];
|
||||
|
||||
// 1. Process Creations and Updates
|
||||
for (const forwardAddress in desiredState) {
|
||||
const forwardToAddresses = desiredState[forwardAddress];
|
||||
|
||||
if (!currentState[forwardAddress]) {
|
||||
createForwardingGroup(forwardAddress, forwardToAddresses, changelog);
|
||||
} else {
|
||||
updateForwardingGroup(forwardAddress, currentState[forwardAddress], forwardToAddresses, changelog);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Process Deletions
|
||||
for (const groupEmail in currentState) {
|
||||
if (!desiredState[groupEmail]) {
|
||||
deleteForwardingGroup(groupEmail, changelog);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Send Admin Report
|
||||
sendReport(desiredState, changelog);
|
||||
|
||||
console.log("Sync complete.");
|
||||
} catch (err) {
|
||||
console.error("Fatal error during syncForwardings:", err);
|
||||
MailApp.sendEmail({
|
||||
to: CONFIG.ADMIN_EMAIL,
|
||||
subject: "[ERROR] Mail Forwarding Sync Failed",
|
||||
body: "The mail forwarding automation encountered a fatal error:\n\n" + err.stack + "\n\nMessage: " + err.message
|
||||
});
|
||||
// Re-throw the error so Apps Script dashboard shows "Failed"
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Google Sheet and builds a map of Source -> [Destinations]
|
||||
*/
|
||||
function readDesiredStateFromSheet() {
|
||||
const ss = CONFIG.SPREADSHEET_ID
|
||||
? SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID)
|
||||
: SpreadsheetApp.getActiveSpreadsheet();
|
||||
|
||||
const sheet = ss.getSheetByName(CONFIG.SHEET_NAME);
|
||||
if (!sheet) {
|
||||
throw new Error(`Sheet '${CONFIG.SHEET_NAME}' not found.`);
|
||||
}
|
||||
|
||||
const data = sheet.getDataRange().getValues();
|
||||
const desiredState = {};
|
||||
|
||||
// Regex for basic email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
// Start at i=1 to skip the header row
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
const row = data[i];
|
||||
let forwardAddress = (row[CONFIG.COL_FORWARD_ADDRESS - 1] || "").toString().trim().toLowerCase();
|
||||
let forwardToAddress = (row[CONFIG.COL_FORWARD_TO_ADDRESS - 1] || "").toString().trim().toLowerCase();
|
||||
|
||||
// Ignore completely empty rows
|
||||
if (!forwardAddress && !forwardToAddress) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. Auto-append domain to forwardAddress if they just typed a name (e.g. "frederic")
|
||||
if (forwardAddress && !forwardAddress.includes("@")) {
|
||||
forwardAddress += `@${CONFIG.WORKSPACE_DOMAIN}`;
|
||||
}
|
||||
|
||||
// 2. Validate forwardAddress domain (MUST be internal)
|
||||
if (forwardAddress && !forwardAddress.endsWith(`@${CONFIG.WORKSPACE_DOMAIN}`)) {
|
||||
console.warn(`Row ${i + 1}: Skipped. Forward address must belong to @${CONFIG.WORKSPACE_DOMAIN} domain. Found: '${forwardAddress}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. Validate forwardAddress is a valid email
|
||||
if (forwardAddress && !emailRegex.test(forwardAddress)) {
|
||||
console.warn(`Row ${i + 1}: Skipped. Invalid forward address format: '${forwardAddress}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Validate forwardToAddress domain (MUST NOT be internal)
|
||||
if (forwardToAddress && forwardToAddress.endsWith(`@${CONFIG.WORKSPACE_DOMAIN}`)) {
|
||||
console.warn(`Row ${i + 1}: Skipped. Destination address cannot be an internal @${CONFIG.WORKSPACE_DOMAIN} address. Found: '${forwardToAddress}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 5. Validate forwardToAddress is a valid email
|
||||
if (forwardToAddress && !emailRegex.test(forwardToAddress)) {
|
||||
console.warn(`Row ${i + 1}: Skipped. Invalid destination email format: '${forwardToAddress}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (forwardAddress && forwardToAddress) {
|
||||
console.log(`Parsed Row ${i + 1}: Valid mapping [${forwardAddress} -> ${forwardToAddress}]`);
|
||||
if (!desiredState[forwardAddress]) {
|
||||
desiredState[forwardAddress] = [];
|
||||
}
|
||||
// Ensure we don't add duplicate destinations for the same source
|
||||
if (desiredState[forwardAddress].indexOf(forwardToAddress) === -1) {
|
||||
desiredState[forwardAddress].push(forwardToAddress);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Row ${i + 1}: Skipped. Missing either forward address or destination (Forward: '${forwardAddress}', To: '${forwardToAddress}')`);
|
||||
}
|
||||
}
|
||||
|
||||
return desiredState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the Admin Directory API for existing Groups managed by this script
|
||||
*/
|
||||
function readCurrentStateFromWorkspace() {
|
||||
const currentState = {};
|
||||
|
||||
// customer: 'my_customer' is a special alias for the primary domain
|
||||
let pageToken;
|
||||
do {
|
||||
const response = AdminDirectory.Groups.list({
|
||||
customer: 'my_customer',
|
||||
pageToken: pageToken
|
||||
});
|
||||
|
||||
const groups = response.groups || [];
|
||||
for (const group of groups) {
|
||||
// ONLY process groups that have our specific safety tag
|
||||
if (group.description && group.description.indexOf(CONFIG.GROUP_DESCRIPTION_TAG) !== -1) {
|
||||
const email = group.email.toLowerCase();
|
||||
currentState[email] = getGroupMembers(email);
|
||||
}
|
||||
}
|
||||
|
||||
pageToken = response.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all members for a specific group
|
||||
*/
|
||||
function getGroupMembers(groupEmail) {
|
||||
const members = [];
|
||||
let pageToken;
|
||||
|
||||
do {
|
||||
const response = AdminDirectory.Members.list(groupEmail, {
|
||||
pageToken: pageToken
|
||||
});
|
||||
|
||||
const memberList = response.members || [];
|
||||
for (const member of memberList) {
|
||||
if (member.email) {
|
||||
members.push(member.email.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
pageToken = response.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
function logAction(changelog, message) {
|
||||
console.log(message);
|
||||
changelog.push(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Workspace Group to act as a forwarder
|
||||
*/
|
||||
function createForwardingGroup(forwardAddress, forwardToAddresses, changelog) {
|
||||
if (CONFIG.DRY_RUN) {
|
||||
logAction(changelog, `[DRY RUN - CREATED] Group: ${forwardAddress}`);
|
||||
for (const forwardTo of forwardToAddresses) {
|
||||
logAction(changelog, ` + [DRY RUN] Added member: ${forwardTo}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newGroup = {
|
||||
email: forwardAddress,
|
||||
name: `Auto-Forwarder: ${forwardAddress}`,
|
||||
description: CONFIG.GROUP_DESCRIPTION_TAG
|
||||
};
|
||||
|
||||
AdminDirectory.Groups.insert(newGroup);
|
||||
logAction(changelog, `[CREATED] Group: ${forwardAddress}`);
|
||||
|
||||
// Propagation delay: Google Workspace APIs are eventually consistent.
|
||||
// We must wait a few seconds after creation before we can add members to the new group.
|
||||
Utilities.sleep(3000);
|
||||
|
||||
for (const forwardTo of forwardToAddresses) {
|
||||
AdminDirectory.Members.insert({ email: forwardTo, role: 'MEMBER' }, forwardAddress);
|
||||
logAction(changelog, ` + Added member: ${forwardTo}`);
|
||||
}
|
||||
} catch (e) {
|
||||
logAction(changelog, `[ERROR] Failed to create group ${forwardAddress}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconciles members of an existing forwarding group
|
||||
*/
|
||||
function updateForwardingGroup(forwardAddress, currentMembers, desiredMembers, changelog) {
|
||||
// 1. Add missing members
|
||||
for (const forwardTo of desiredMembers) {
|
||||
if (currentMembers.indexOf(forwardTo) === -1) {
|
||||
if (CONFIG.DRY_RUN) {
|
||||
logAction(changelog, `[DRY RUN - UPDATED] Group ${forwardAddress}: Added member ${forwardTo}`);
|
||||
} else {
|
||||
try {
|
||||
AdminDirectory.Members.insert({ email: forwardTo, role: 'MEMBER' }, forwardAddress);
|
||||
logAction(changelog, `[UPDATED] Group ${forwardAddress}: Added member ${forwardTo}`);
|
||||
} catch (e) {
|
||||
logAction(changelog, `[ERROR] Failed to add member ${forwardTo} to ${forwardAddress}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Remove obsolete members
|
||||
for (const current of currentMembers) {
|
||||
if (desiredMembers.indexOf(current) === -1) {
|
||||
if (CONFIG.DRY_RUN) {
|
||||
logAction(changelog, `[DRY RUN - UPDATED] Group ${forwardAddress}: Removed member ${current}`);
|
||||
} else {
|
||||
try {
|
||||
AdminDirectory.Members.remove(forwardAddress, current);
|
||||
logAction(changelog, `[UPDATED] Group ${forwardAddress}: Removed member ${current}`);
|
||||
} catch (e) {
|
||||
logAction(changelog, `[ERROR] Failed to remove member ${current} from ${forwardAddress}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a forwarding group (Requires the safety tag check performed during read)
|
||||
*/
|
||||
function deleteForwardingGroup(groupEmail, changelog) {
|
||||
if (CONFIG.DRY_RUN) {
|
||||
logAction(changelog, `[DRY RUN - DELETED] Group: ${groupEmail}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
AdminDirectory.Groups.remove(groupEmail);
|
||||
logAction(changelog, `[DELETED] Group: ${groupEmail}`);
|
||||
} catch (e) {
|
||||
logAction(changelog, `[ERROR] Failed to delete group ${groupEmail}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a summary report to the configured Admin Email
|
||||
*/
|
||||
function sendReport(desiredState, changelog) {
|
||||
if (changelog.length === 0) {
|
||||
console.log("No changes detected. Skipping email report.");
|
||||
return;
|
||||
}
|
||||
|
||||
const subject = "[Haumdaucher] Mail Forwarding Sync Report";
|
||||
|
||||
let body = "The mail forwarding automation has executed successfully.\n\n";
|
||||
|
||||
body += "=== CHANGELOG ===\n";
|
||||
body += changelog.join("\n") + "\n\n";
|
||||
|
||||
body += "=== ACTIVE FORWARDINGS ===\n";
|
||||
const sources = Object.keys(desiredState).sort();
|
||||
if (sources.length === 0) {
|
||||
body += "No active forwardings configured in the sheet.\n";
|
||||
} else {
|
||||
for (const source of sources) {
|
||||
body += `${source} -> ${desiredState[source].join(", ")}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
MailApp.sendEmail({
|
||||
to: CONFIG.ADMIN_EMAIL,
|
||||
subject: subject,
|
||||
body: body
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"timeZone": "Europe/Berlin",
|
||||
"dependencies": {
|
||||
"enabledAdvancedServices": [
|
||||
{
|
||||
"userSymbol": "AdminDirectory",
|
||||
"serviceId": "admin",
|
||||
"version": "directory_v1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"exceptionLogging": "STACKDRIVER",
|
||||
"oauthScopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.group",
|
||||
"https://www.googleapis.com/auth/script.send_mail",
|
||||
"https://www.googleapis.com/auth/spreadsheets",
|
||||
"https://www.googleapis.com/auth/script.scriptapp"
|
||||
],
|
||||
"runtimeVersion": "V8"
|
||||
}
|
||||
Reference in New Issue
Block a user