All Resources
Feature Spotlight 15 min read April 12, 2026

MoveRight Scripts: Automate Your Moving Company Workflows

A complete guide to writing scripts that automate lead follow-up, estimate reminders, booking confirmations, and more. Includes hook reference, host functions, TSX templates, and sample scripts.

Scripts let you automate repetitive workflows in your moving company. When a new lead comes in, a script can trigger Ask Bee to call the customer, send an SMS, and queue follow-ups for the next 5 days. When an estimate is signed, a script can add valuation charges, send a booking confirmation, and assign the crew.

This guide covers everything you need to write, test, and deploy scripts.

Quick Start

The fastest way to get started:

  1. Go to Settings → Scripts in your zone
  2. Click Add Script
  3. Choose Script URL and select a sample like “Web Lead Followup”
  4. Click Create — it auto-runs the install hook to set up tags

How Scripts Work

┌─────────────────────────────────────────────────────────────────────┐
│                         Trigger Event                                │
│   (job-created, job-stage-changed, tag-added, custom, etc.)         │
└─────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│                      Script Matching Query                           │
│   Find enabled scripts with matching hook in same or ancestor zone  │
└─────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│                      QuickJS Sandbox Execution                       │
│   1. Pre-fetch zone branding (__branding global)                     │
│   2. Inject host functions (sendSMS, addComment, etc.)               │
│   3. Evaluate script → runs hook registrations                     │
│   4. Call registered hook callback with context                    │
└─────────────────────────────────────────────────────────────────────┘

Scripts register listeners for triggers using hook(triggerName, callback). When the trigger fires, the callback runs with a context object containing job data, users, events, and more.

Script Anatomy

/// <reference path="./script.d.ts" />
/** @jsx h */
/** @jsxFrag Fragment */

/**
 * @name My Script Name
 * @description What this script does.
 * @hooks job-created, custom-hook-1, install
 */

hook('job-created', (ctx) => {
  const job = ctx.data.job;
  if (!job) return;
  
  sendSMS({ body: 'Welcome!' });
  addTag(ctx, 'New Lead');
});

hook('install', async (ctx) => {
  log('Setting up...');
  await createZoneTag('New Lead');
});

Metadata Comments

Extracted automatically and displayed in the UI:

  • @name — Script display name
  • @description — Brief description
  • @hooks — List of hooks (used for filtering)

The install Hook

Scripts with an install hook auto-run it on creation. Use it to:

  • Create required tags
  • Log setup instructions
  • Check zone configuration
hook('install', async (ctx) => {
  log('Setting up...');
  await Promise.all([
    createZoneTag('New Lead'),
    createZoneTag('Lead Day:Day 1'),  // GitLab-style scoped label
  ]);
  log('Setup complete!');
});

Available Triggers

TriggerWhen it fires
job-createdNew job created
job-stage-changedJob moved to new stage (lead → estimate → booking → invoice)
job-status-changedJob opened or closed
tag-addedA tag was added to the job
tag-removedA tag was removed from the job
email-receivedCustomer replied via email
sms-receivedCustomer replied via SMS
estimate-signedCustomer signed an estimate
email-link-clickedCustomer clicked a link in an email
job-event-createdNew event added to job
comment-createdNew comment on job

Custom Triggers

Use schedule() to create custom triggers:

schedule('my-followup', '1 hour', { attempt: 1 }, ctx);

hook('my-followup', (ctx) => {
  // Runs 1 hour later
});

Host Functions Reference

Communication

sendSMS(input: { body: string; to?: string })

Sends an SMS to the job’s customer. Omit to to send to all customer phones.

sendSMS({
  body: `Hi ${name}, thanks for reaching out!`,
});

sendEmail(input: { subject: string; html?: string; text?: string; markdown?: string })

Sends an email to the job’s customer.

sendEmail({
  subject: 'Welcome!',
  html: '<p>Thanks for choosing us!</p>',
});

addCommentToJob(input: { text?: string; mode?: string; attributes?: string[] })

Adds a comment to the job activity feed.

Common modes:

  • 'System' — Internal note (staff only)
  • 'Ask Bee' — Triggers AI assistant (requires 'AI_RESPONSE_REQUESTED' in attributes)
  • 'Send SMS to Customer' — Outbound SMS
  • 'Send Email to Customer' — Outbound email
// Trigger Ask Bee
addCommentToJob({
  mode: 'Ask Bee',
  text: 'Call the customer to confirm the move date.',
  attributes: ['TRIGGER', 'SYSTEM', 'AI_RESPONSE_REQUESTED'],
});

// Log system action
addCommentToJob({
  mode: 'System',
  text: 'Script closed job due to opt-out.',
});

Tags

hasTag(ctx: ScriptContext, tagName: string): boolean

Checks if the job has a tag.

if (hasTag(ctx, 'Opt Out')) {
  return;
}

addTag(ctx: ScriptContext, tagName: string)

Adds a tag to the job.

addTag(ctx, 'New Lead');

removeTag(ctx: ScriptContext, tagName: string)

Removes a tag from the job.

removeTag(ctx, 'New Lead');

Job Status

closeJob(reason: string)

Closes the job with the specified reason.

closeJob('could_not_contact');
closeJob('opt_out');

Scheduling

schedule(triggerName: string, delay: string | number, data: object, ctx: ScriptContext, opts?: { key?: string })

Schedules a future hook execution.

Delay formats: '5 min', '2 hours', '1 day', or milliseconds.

// One-time callback
schedule('followup', '3 hours', { attempt: 1 }, ctx);

// Rolling daily check (key overwrites previous)
schedule('daily-check', '24 hours', { day: 1 }, ctx, { key: 'daily' });

Charges

addCharge(input: { productName?: string; amount?: number })

Adds a charge to the job. Idempotent by product name.

addCharge({
  productName: 'Fuel Surcharge',
  amount: 75,
});

deleteCharge(chargeId: string)

Deletes a charge.

deleteCharge('charge-123');

Zone Setup (install hook only)

createZoneTag(tagName: string): Promise<boolean>

Creates a tag in the zone. Returns true if created, false if exists.

await createZoneTag('New Lead');

Logging

log(...args: any[])

Logs to the server-side logger and returns to frontend for install hooks.

log('Script started', job.code);

Injected Globals

__branding

Zone branding config, pre-fetched before execution.

const { companyName, phone, primaryColor, logoUrl } = __branding;

sendSMS({
  body: `Hi! This is ${companyName}. Call us at ${phone}.`,
});

Available: companyName, appName, tagline, primaryColor, secondaryColor, logoUrl, logoSquareUrl, website, phone, email, address, aiName, businessHours.

jobCharges

Array of all charges across job events.

const hasFuelSurcharge = jobCharges.some(c => c.productName === 'Fuel Surcharge');

TSX Email Templates

Scripts can use JSX/TSX for HTML emails. A minimal runtime is injected:

/** @jsx h */
/** @jsxFrag Fragment */

function WelcomeEmail({ name, branding }) {
  return (
    <table width="600" style={{ margin: '0 auto' }}>
      <tr>
        <td style={{ padding: '24px' }}>
          <h2 style={{ color: branding.primaryColor }}>Hi {name}!</h2>
          <p>Welcome to {branding.companyName}.</p>
        </td>
      </tr>
    </table>
  );
}

hook('job-created', (ctx) => {
  const html = renderToString(<WelcomeEmail name={firstName(ctx)} branding={__branding} />);
  sendEmail({ subject: 'Welcome!', html });
});

Style props support both objects { fontSize: '14px' } and strings "font-size: 14px".

Sample Scripts

Web Lead Followup

5-day automated sequence for online leads (OBE, Website, Lead Aggregator):

  • Ask Bee autodial on creation
  • Welcome SMS after 3 minutes
  • Daily Ask Bee follow-ups
  • TSX email templates with moving tips
  • Auto-close after 5 days

Estimate Followup

5-day sequence after estimate sent:

  • Daily Ask Bee check-ins
  • Value proposition emails
  • Escalation to sales lead

Valuation Coverage

Automated valuation selection:

  • Sends coverage options email when job is booked
  • Handles customer click to add charge
  • Sends confirmation email

Type Declarations

Full type definitions at script.d.ts.

Best Practices

  1. Filter early — Return early for irrelevant triggers
  2. Use install hooks — Document setup requirements
  3. Schedule with keys — Prevent duplicate pending jobs
  4. Close jobs explicitly — Use closeJob(reason) when sequences end
  5. Personalize with __branding — Use zone-specific names, colors, contact info
  6. Add AI_RESPONSE_REQUESTED — For Ask Bee comments to work properly

Restrictions

  • No call() function — Can’t directly call Moleculer services
  • SSRF protectionfetch() can’t reach private IPs
  • 5 fetch calls max per execution
  • 60 second timeout per script
  • No filesystem access — Runs in sandboxed QuickJS VM

Getting Help

MR

MoveRight Team

MoveRight

scripts automation developer hooks API

Ready to put this into practice?

Start a 5-day free trial and see how MoveRight handles this in your business.