A NetSuite SuiteApp that adds browser-to-phone calling directly from CRM records. Sales reps click a phone icon on Customer, Contact, or Lead records; a softphone popup opens; calls place via Twilio. Recordings transcribe automatically and are analyzed by NetSuite's N/llm module to produce Phone Call activity records with AI summaries, satisfaction scores, tone keywords, and action items.
Zero external hosting required — every script runs on NetSuite SuiteScript 2.1; everything else is Twilio's managed platform. No middleware, no Node.js server, no external services.
Important
SuiteApp — verify in your account before production use
This repository is an open-source example of a NetSuite + Twilio click-to-call integration with AI call analysis. It is provided for educational and demonstration purposes only. There is no warranty of any kind; use it at your own risk. The code is not certified for quality, security, correctness, or production readiness in your environment.
Twilio usage incurs real costs (Voice, Recording, Conversational Intelligence) and calls are recorded — ensure you meet consent and data-handling requirements for your jurisdiction. Configure the CTC Configuration record, test with the same roles that will place calls, and review execution logs in a sandbox before relying on this in production.
The Click-to-Call Admin Console (Setup Wizard SPA) — deployment status, provisioned phone numbers, assigned reps, preflight checks, and recent call activity with a per-rep breakdown.
The softphone popup launched from a Customer record — contact info, dialpad, and one-click calling via Twilio Voice, all same-origin with NetSuite.
Completed calls land as Phone Call activity records, enriched with AI briefs, tone keywords, satisfaction scores, and duration.
The Call Intelligence panel on a Phone Call record — an N/llm-generated summary, satisfaction score, full transcript, and extracted action items.
- One-click calling from Customer, Contact, and Lead records
- Softphone popup — dial, mute, hold, contact-info side panel
- Sales Rep Central portlet — recent calls + rollups at-a-glance
- Setup Wizard SPA — UIF-based 5-step onboarding + multi-section Admin Console
- Automatic recording with two-party consent disclosure
- AI transcription + analysis — Twilio Conversational Intelligence + N/llm (Cohere Command A)
- Phone Call activity records with full transcript, summary, score, tone keywords, action items
- Recording cleanup — Twilio recordings auto-deleted after transcript extraction
- Multi-role support — Admin + Sales Rep custom roles
CALL FLOW (synchronous)
User Event (phone icon)
│
Suitelet (softphone popup HTML + Twilio Voice JS SDK 2.x)
│
RESTlet (mints Twilio JWT via N/crypto HMAC-SHA256)
│
Browser ─── Twilio Voice ─── PSTN
ENRICHMENT (async, polled every 15 min)
Scheduled Script
│
Twilio Recordings API + Conversational Intelligence
│
N/llm (Cohere Command A)
│
Phone Call record updated with transcript + AI fields
│
Twilio recording deleted (cost containment)
| Layer | Technology |
|---|---|
| Phone icon injection | UserEvent Script (beforeLoad on Customer/Contact/Lead) |
| Softphone UI | Suitelet-served HTML + Twilio Voice JS SDK 2.7.3 |
| Setup wizard + admin console | UIF SPA (@uif-js/core + @uif-js/component) |
| Auth | RESTlet mints Twilio JWTs using N/crypto HMAC-SHA256 |
| Transcription | Twilio Conversational Intelligence (async, $0.035/min) |
| AI analysis | N/llm (Cohere Command A, free tier ~1000 req/mo) |
| Polling | Scheduled Script every 15 min on weekdays |
| Sales Rep dashboard | Portlet (Sales Rep Central) |
| Packaging | SDF SuiteApp (com.netsuite.clicktocall) |
src/
├── FileCabinet/SuiteApps/com.netsuite.clicktocall/
│ ├── ctc_pl_dashboard.js Portlet — Sales Rep Central
│ ├── ctc_rl_token.js RESTlet — JWT mint + softphone actions
│ ├── ctc_sl_softphone.js Suitelet — softphone popup HTML
│ ├── ctc_sl_wizard_api.js Suitelet — setup wizard JSON API
│ ├── ctc_ss_poll_transcripts.js Scheduled Script — poll Twilio
│ ├── ctc_ue_phone_button.js UserEvent — phone icon
│ ├── ctc_ue_transcript_viewer.js UserEvent — Call Intelligence panel
│ ├── lib/ Shared SuiteScript modules + Twilio SDK
│ └── setup_wizard/ SPA bundle output (generated, see below)
│ ├── SpaClient.js
│ └── SpaServer.js
├── SuiteApps/com.netsuite.clicktocall/
│ └── setup_wizard/ SPA TypeScript source
│ ├── SpaClient.tsx Entry point (5-line, mounts <App />)
│ ├── SpaServer.ts Server endpoints
│ ├── App.tsx Root PureComponent — subscribes to Store
│ ├── AppController.tsx Imperative builders + handlers + loaders
│ ├── app/ State machinery (Action, Reducer, InitialState, Store)
│ ├── components/ UI building blocks (matches airport360 canonical)
│ │ ├── shared/ Cross-feature primitives (was render/)
│ │ ├── sections/ Admin Console sections (overview/phones/voice/...)
│ │ └── steps/ Onboarding wizard step forms
│ └── services/ Server data fetching (wizardApi)
├── Objects/
│ ├── Records/ customrecord_*.xml
│ ├── Scripts/ customscript_*.xml + custspa_*.xml
│ ├── Fields/ custevent_*.xml (Phone Call CRM fields)
│ └── Lists/ customlist_*.xml
├── manifest.xml
└── deploy.xml
- NetSuite account with SuiteScript 2.1, SDF, and UIF (2025.1+)
- Twilio account with:
- Voice-capable phone number
- API Key + Secret
- TwiML App pointing to a TwiML Bin
- Conversational Intelligence service enabled
- Node.js 20+ (for local development, testing, and the bundle pipeline)
@oracle/suitecloud-cliconfigured with M2M auth (see Deploy below)
npm install
npm test # 524 jest tests
npm run lint # eslint-plugin-suitescript
npm run typecheck # tsc --noEmit (TS source validation)
npm run bundle # gulp + rollup — regenerate SPA bundle from TS source# Set up M2M credentials once (one-time per machine):
suitecloud account:setup:ci --account <accountId> --authid <id> \
--certificateid <certId> --privatekeypath ./private-key.pem
# Deploy (bundles SPA + pushes to NetSuite):
npm run deployThe npm run deploy script chains gulp bundle (TS → AMD JS) then
suitecloud project:deploy. The SPA bundle output (SpaClient.js,
SpaServer.js) is committed to git alongside its TypeScript source so a
fresh git clone + npm run deploy works without an extra bundle step.
Before the first deploy, set up Twilio:
- Purchase a phone number with Voice capability
- Create an API Key (Standard) — save the SID and Secret
- Create a TwiML Bin with call recording and consent disclosure
- Create a TwiML App pointing to the TwiML Bin URL
- Enable Conversational Intelligence with auto-transcribe
After first deploy:
- CTC Configuration record — create a singleton instance with Twilio credentials (Account SID, API Key SID, API Secret pointer)
- API Secret — store the Twilio API Key Secret value as a
custsecret_*NetSuite API Secret; reference its script ID in the config record - Setup Wizard — admins open the Setup Wizard SPA from the Click-to-Call deployment to complete configuration
npm test # 524 tests across 15 suites
npm test -- --watch # watch modeTests use @oracle/suitecloud-unit-testing for SuiteScript module stubs, plus
a custom N/llm mock at __mocks__/llm.js. The __tests__/ and __mocks__/
folders are kept local-only and are not included in the published repository.
GitHub Actions runs on every PR + push to main:
- ESLint (
eslint-plugin-suitescript) - TypeScript typecheck (
tsc --noEmit) - Bundle drift gate (regenerates SPA and fails if committed bundle is stale)
The Jest suite runs locally (npm test), not in CI — the test files are kept
local-only (see Testing). See .github/workflows/ci.yml.
For 10 reps making ~20 calls/day (5 min avg):
| Component | Monthly |
|---|---|
| Twilio Phone Number | $1.15 |
| Outbound Voice | $42.00 |
| Recording Storage | $1.50 |
| Conversational Intelligence | $105.00 |
| N/llm (free tier) | $0.00 |
| Total | ~$150/mo |



