Client Onboarding Pipeline
Module 16 · business · intermediate
plan.js
// Validate the new client and turn the package template into a concrete onboarding plan:
// names for the folder/channel, dated tasks, and the welcome email.
const s = $('Settings (edit me)').first().json;
const req = $('New Client').first().json;
const b = req.body ?? {};
const errors = [];
const clientName = String(b.client_name ?? '').trim();
const contactName = String(b.contact_name ?? '').trim();
const contactEmail = String(b.contact_email ?? '').trim().toLowerCase();
const pkgKey = String(b.package ?? '').trim().toLowerCase();
const pkg = s.packages[pkgKey];
if (!clientName) errors.push('client_name is required');
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(contactEmail)) errors.push('contact_email must be a valid email');
if (!pkg) errors.push(`package must be one of: ${Object.keys(s.packages).join(', ')}`);
let start = b.start_date ? DateTime.fromISO(b.start_date) : DateTime.now().plus({ days: 1 });
if (b.start_date && !start.isValid) errors.push('start_date must be YYYY-MM-DD');
if (errors.length) return [{ json: { valid: false, errors } }];
// Business days only (Mon–Fri)
const addBusinessDays = (d, n) => {
let x = d;
while (x.weekday > 5) x = x.plus({ days: 1 });
for (let i = 0; i < n; i++) { x = x.plus({ days: 1 }); while (x.weekday > 5) x = x.plus({ days: 1 }); }
return x;
};
start = addBusinessDays(start.startOf('day'), 0);
const slug = clientName.toLowerCase().normalize('NFKD').replace(/[^\w\s-]/g, '').trim().replace(/[\s_]+/g, '-').replace(/-+/g, '-').slice(0, 60);
const first = contactName.split(' ')[0] || 'there';
const kickoffBy = addBusinessDays(start, pkg.kickoff_within_days);
const tasks = pkg.tasks.map((t, i) => ({
n: i + 1,
name: `[${clientName}] ${t.name}`,
owner_role: t.owner_role,
due: addBusinessDays(start, t.offset_days).toISODate(),
}));
return [{
json: {
valid: true,
dry_run: req.query?.dry_run === 'true',
force: b.force === true,
client_name: clientName,
contact_name: contactName,
contact_email: contactEmail,
package: pkgKey,
package_label: pkg.label,
slug,
start_date: start.toISODate(),
kickoff_by: kickoffBy.toISODate(),
folder_name: `${clientName} (${start.toFormat('yyyy-LL')})`,
channel_name: `${s.slack_channel_prefix}${slug}`.slice(0, 80),
tasks,
email: {
to: contactEmail,
subject: `Welcome to ${s.agency_name}, ${first}! Your next steps`,
body:
`Hi ${first},\n\n` +
`Welcome aboard! We're excited to start your ${pkg.label} engagement on ${start.toFormat('cccc, LLLL d')}.\n\n` +
`Here's what happens next:\n` +
`1. Book your kickoff call (ideally before ${kickoffBy.toFormat('LLLL d')}): ${s.kickoff_link}\n` +
`2. You'll get access to a shared folder for files and assets.\n` +
`3. Before the kickoff, please send logins/access for: ${pkg.access_needed.join(', ')}.\n\n` +
`Any questions, just reply to this email.\n\n` +
`Talk soon,\n${s.sender_name}\n${s.agency_name}`,
},
},
}];