Follow these four steps to connect Network Momentum to your Google Sheet. You only need to do this once. Chrome is recommended.
Network Momentum stores your contacts in a Google Sheet that you own. Your data never leaves your Google account.
Contacts
Google Apps Script is a free tool built into Google Sheets. Paste the script below exactly as written do not change anything.
function doGet(e) { return handleRequest(e); }
function doPost(e) {
var p = JSON.parse(e.postData.contents);
return handleAction(p);
}
function handleRequest(e) {
var p = e.parameter;
return handleAction(p);
}
function authorizeCalendar() {
CalendarApp.getDefaultCalendar();
}
function installStaleFollowUpTrigger() {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction() === "checkStaleFollowUps") {
ScriptApp.deleteTrigger(triggers[i]);
}
}
ScriptApp.newTrigger("checkStaleFollowUps")
.timeBased()
.everyDays(1)
.atHour(6)
.create();
}
function checkStaleFollowUps() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Contacts");
var rows = sheet.getDataRange().getValues();
var staleDays = 7;
var now = new Date();
var stale = [];
for (var i = 1; i < rows.length; i++) {
var row = rows[i];
var name = row[1];
var company = row[2];
var status = row[4];
var addedAt = row[7];
var tags = row[9] || "";
if (status !== "Follow up Email") continue;
var changedDate = addedAt;
var match = String(tags).match(/statuschanged:([0-9]{4}-[0-9]{2}-[0-9]{2})/);
if (match) changedDate = match[1];
if (!changedDate) continue;
var days = Math.floor((now - new Date(changedDate)) / 86400000);
if (days >= staleDays) {
stale.push({ name: name, company: company, days: days });
}
}
if (stale.length === 0) return;
var lines = stale.map(function(c) {
return "- " + c.name + (c.company ? " (" + c.company + ")" : "") +
" \u2014 " + c.days + " days in Follow up Email";
});
var body = "These contacts have been sitting in \"Follow up Email\" status for 7+ days:\n\n" +
lines.join("\n") +
"\n\nOpen Network Momentum: https://networkmomentum.app";
MailApp.sendEmail(
Session.getActiveUser().getEmail(),
"Network Momentum: " + stale.length + " stale follow-up" + (stale.length > 1 ? "s" : ""),
body
);
}
function installWeeklyDigestTrigger() {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction() === "sendWeeklyDigest") {
ScriptApp.deleteTrigger(triggers[i]);
}
}
ScriptApp.newTrigger("sendWeeklyDigest")
.timeBased()
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(7)
.create();
}
function sendWeeklyDigest() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Contacts");
var rows = sheet.getDataRange().getValues();
var now = new Date();
var weekAgo = new Date(now.getTime() - 7 * 86400000);
var newThisWeek = [];
var overdue = [];
var stuckFollowUp = [];
for (var i = 1; i < rows.length; i++) {
var row = rows[i];
var name = row[1];
var company = row[2];
var status = row[4];
var lastContact = row[5];
var addedAt = row[7];
var tags = row[9] || "";
if (addedAt) {
var addedDate = new Date(addedAt);
if (addedDate >= weekAgo) {
newThisWeek.push({ name: name, company: company });
}
}
if (lastContact && status !== "Closed" && status !== "Follow-up needed" && status !== "Not contacted") {
var daysSinceContact = Math.floor((now - new Date(lastContact)) / 86400000);
if (daysSinceContact >= 7) {
overdue.push({ name: name, company: company, days: daysSinceContact });
}
}
if (status === "Follow up Email") {
var changedDate = addedAt;
var match = String(tags).match(/statuschanged:([0-9]{4}-[0-9]{2}-[0-9]{2})/);
if (match) changedDate = match[1];
if (changedDate) {
var daysInStatus = Math.floor((now - new Date(changedDate)) / 86400000);
if (daysInStatus >= 5) {
stuckFollowUp.push({ name: name, company: company, days: daysInStatus });
}
}
}
}
if (newThisWeek.length === 0 && overdue.length === 0 && stuckFollowUp.length === 0) return;
var body = "Your Network Momentum weekly digest:\n";
body += "\nNew contacts this week (" + newThisWeek.length + "):\n";
if (newThisWeek.length === 0) {
body += "- None\n";
} else {
newThisWeek.forEach(function(c) {
body += "- " + c.name + (c.company ? " (" + c.company + ")" : "") + "\n";
});
}
body += "\nOverdue, 7+ days since last contact (" + overdue.length + "):\n";
if (overdue.length === 0) {
body += "- None\n";
} else {
overdue.forEach(function(c) {
body += "- " + c.name + (c.company ? " (" + c.company + ")" : "") + " \u2014 " + c.days + "d\n";
});
}
body += "\nStuck in Follow up Email, 5+ days (" + stuckFollowUp.length + "):\n";
if (stuckFollowUp.length === 0) {
body += "- None\n";
} else {
stuckFollowUp.forEach(function(c) {
body += "- " + c.name + (c.company ? " (" + c.company + ")" : "") + " \u2014 " + c.days + "d\n";
});
}
body += "\nOpen Network Momentum: https://networkmomentum.app";
MailApp.sendEmail(
Session.getActiveUser().getEmail(),
"Network Momentum weekly digest",
body
);
}
function handleAction(p) {
var out = {};
if (p.action === "headers") {
out = { headers: ["ID","Name","Company","Role","Status",
"LastContact","Notes","AddedAt","Priority","Tags",
"LinkedIn","NotesHistory","Email"] };
} else if (p.action === "read") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Contacts");
var rows = sheet.getDataRange().getValues();
out = { rows: rows };
} else if (p.action === "append") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Contacts");
var row = JSON.parse(p.row);
sheet.appendRow(row);
out = { success: true };
} else if (p.action === "append_batch") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Contacts");
var newRows = JSON.parse(p.rows);
if (!newRows || !newRows.length) {
out = { error: "No rows provided" };
} else {
var lastRow = sheet.getLastRow();
sheet.getRange(lastRow + 1, 1, newRows.length, newRows[0].length).setValues(newRows);
out = { success: true, count: newRows.length };
}
} else if (p.action === "update") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Contacts");
sheet.getRange(parseInt(p.row), parseInt(p.col)).setValue(p.value);
out = { success: true };
} else if (p.action === "clear") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Contacts");
sheet.getRange(parseInt(p.row), 1, 1, 13).clearContent();
out = { success: true };
} else if (p.action === "create_event") {
var title = p.title || "Follow up";
var description = p.description || "";
var start = new Date(p.start);
var minutes = parseInt(p.duration) || 30;
var end = new Date(start.getTime() + minutes * 60000);
try {
var event = CalendarApp.getDefaultCalendar()
.createEvent(title, start, end, { description: description });
out = { success: true, eventId: event.getId() };
} catch (err) {
out = { error: err.message };
}
} else if (p.action === "ai_suggest") {
var apiKey = p.apiKey;
var prompt = p.prompt;
if (!apiKey || !prompt) {
out = { error: "Missing apiKey or prompt" };
} else {
var payload = JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 1000,
messages: [{ role: "user", content: prompt }]
});
var options = {
method: "post",
contentType: "application/json",
headers: { "x-api-key": apiKey,
"anthropic-version": "2023-06-01" },
payload: payload,
muteHttpExceptions: true
};
try {
var resp = UrlFetchApp.fetch(
"https://api.anthropic.com/v1/messages", options);
var result = JSON.parse(resp.getContentText());
if (result.content && result.content[0]) {
out = { suggestion: result.content[0].text };
} else {
out = { error: "No response from AI" };
}
} catch(err) {
out = { error: err.message };
}
}
} else {
out = { error: "unknown action" };
}
return ContentService
.createTextOutput(JSON.stringify(out))
.setMimeType(ContentService.MimeType.JSON);
}
Deploying the script creates a secure URL that Network Momentum uses to communicate with your Google Sheet.
https://script.google.com/macros/s/XXXXXXXXXX/exec. Click Copy next to it. You will need this in the next step.Now connect Network Momentum to your Google Sheet.
"Could not connect" error:
/exec at the end.No contacts loading:
Contacts with a capital C.AI features not working:
sk-ant-. Get one at console.anthropic.com. New accounts typically include a small trial credit. After that, usage is billed per token. Check current amounts at console.anthropic.com. For light personal use, costs stay very low.sk-. Get one at platform.openai.com. The API is a separate product from Claude.ai and ChatGPT. It is billed per use regardless of any existing subscription.Need help? rebeca@plainspeakingcomms.com
Network Momentum can help you block time for follow-ups two ways: a one-click button that adds an event straight to Google Calendar, or a downloadable calendar file (.ics) that works with macOS Calendar, Outlook, and most other calendar apps.
If you followed Step 2 above using the current code block, the Calendar feature is already included in your script. You just need to grant it permission once:
doGet).Seeing an error that says unknown action when you try to schedule something? That means your script doesn't have the Calendar code yet, re-copy Step 2 and try again. An error mentioning permission or authorization means you need to run the authorizeCalendar step above.
Contacts sitting in Follow up Email status get a badge on their card after 3 days, and turn to a Stale badge after 7. If you'd also like a daily email reminder when contacts hit 7+ days, run this one-time setup:
If you followed Step 2 above using the current code block, this feature is already included in your script. You just need to install the daily check once:
Want a single weekly summary instead of checking the app? This sends one email every Monday morning covering: new contacts added that week, anyone overdue (7+ days since last contact), and anyone stuck in Follow up Email status (5+ days).
If you followed Step 2 above using the current code block, this feature is already included in your script. You just need to install the weekly check once:
You're all set. Open the app and add your first contact.
Launch Network Momentum →