Skip to main content

๐Ÿข Brand Status Check (A2P)

๐Ÿ“– Overviewโ€‹

The Brand Status Check job monitors Twilio Application-to-Person (A2P) brand registrations and automatically provisions messaging infrastructure when brands are verified. It runs every minute, identifies accounts with pending or recently approved brands (both sole proprietor and low volume standard types), checks brand verification status with Twilio, and automatically creates messaging services and campaigns for verified brands. This ensures SMS sending compliance with 10DLC (10-Digit Long Code) regulations.

Complete Flow:

  1. Cron Initialization: queue-manager/crons/communication/a2p/brand.js
  2. Service Processing: queue-manager/services/communication/a2p.js (brandStatusCheck)
  3. Queue Definition: queue-manager/queues/communication/a2p/brand.js

Execution Pattern: Frequent polling (every minute) with account-level queuing

Queue Name: comm_a2p_brandCheck

Environment Flag: QM_COMMUNICATION_A2P_BRAND=true (in index.js)

๐Ÿ”„ Complete Processing Flowโ€‹

sequenceDiagram
participant CRON as Cron Schedule<br/>(every 1 min)
participant SERVICE as Brand Status<br/>Service
participant ACCOUNT_DB as Accounts DB
participant QUEUE as Brand Check<br/>Queue
participant TWILIO as Twilio A2P API

CRON->>SERVICE: brandStatusCheck()
SERVICE->>ACCOUNT_DB: Aggregate accounts where:<br/>- Brand status = PENDING OR<br/>- Brand APPROVED + identity UNVERIFIED OR<br/>- Brand APPROVED + VERIFIED + no campaign
ACCOUNT_DB-->>SERVICE: List of accounts

loop Each account
SERVICE->>QUEUE: Add job: {account}
end

loop Each queued account
QUEUE->>QUEUE: Determine brand type:<br/>- sole_proprietor_brand<br/>- low_volume_standard_brand

QUEUE->>TWILIO: GET /BrandRegistrations/{sid}<br/>Fetch brand status
TWILIO-->>QUEUE: Brand details:<br/>status, identityStatus

QUEUE->>ACCOUNT_DB: Update account:<br/>twilio_account.a2p.{type} = brand

alt Brand APPROVED + VERIFIED
alt Messaging service not exists
QUEUE->>TWILIO: POST /Services<br/>Create messaging service
TWILIO-->>QUEUE: Service SID
QUEUE->>ACCOUNT_DB: Store service SID
end

QUEUE->>QUEUE: Build campaign payload:<br/>- Use case description<br/>- Message flow/consent<br/>- Sample messages<br/>- Opt-in/out keywords (standard only)

QUEUE->>TWILIO: POST /Services/{sid}/UsAppToPerson<br/>Create messaging campaign
TWILIO-->>QUEUE: Campaign SID + status

QUEUE->>ACCOUNT_DB: Store campaign data:<br/>- campaignSid<br/>- campaignStatus (IN_PROGRESS)
else Brand not verified
QUEUE->>QUEUE: Skip campaign creation<br/>(wait for next check)
end
end

๐Ÿ“ Source Filesโ€‹

1. Cron Initializationโ€‹

File: queue-manager/crons/communication/a2p/brand.js

Purpose: Schedule brand status check every minute

Cron Pattern: * * * * * (every minute)

Initialization:

const { brandStatusCheck } = require('../../../services/communication/a2p');
const cron = require('node-cron');
const logger = require('../../../utilities/logger');

let inProgress = false;
exports.start = async () => {
try {
cron.schedule('* * * * *', async () => {
if (!inProgress) {
inProgress = true;
await brandStatusCheck();
inProgress = false;
}
});
} catch (err) {
logger.error({ initiator: 'QM/communication/a2p/brand-status', error: err });
}
};

In-Progress Lock: Prevents overlapping executions.

2. Service Processingโ€‹

File: queue-manager/services/communication/a2p.js (brandStatusCheck export)

Purpose: Find accounts with pending/approved brands and queue for verification

Key Features:

  • Complex aggregation matching multiple brand states
  • Supports both sole proprietor and low volume standard brands
  • Batch job queuing with exponential backoff retry
  • Promise.all for parallel queuing

Main Service Function:

const brandStatusCheck = require('../../queues/communication/a2p/brand');
const Account = require('../../models/account');
const logger = require('../../utilities/logger');

exports.brandStatusCheck = async () => {
try {
let accounts = await Account.aggregate([
{
$match: {
$or: [
{
$or: [
{ 'twilio_account.a2p.sole_proprietor_brand.status': 'PENDING' },
{
'twilio_account.a2p.sole_proprietor_brand.status': 'APPROVED',
'twilio_account.a2p.sole_proprietor_brand.identityStatus': 'UNVERIFIED',
},
{
'twilio_account.a2p.sole_proprietor_brand.status': 'APPROVED',
'twilio_account.a2p.sole_proprietor_brand.identityStatus': 'VERIFIED',
'twilio_account.a2p.messaging_service': { $exists: 0 },
'twilio_account.a2p.messaging_campaign': { $exists: 0 },
},
{
'twilio_account.a2p.sole_proprietor_brand.status': 'APPROVED',
'twilio_account.a2p.sole_proprietor_brand.identityStatus': 'VERIFIED',
'twilio_account.a2p.messaging_service': { $exists: 1 },
'twilio_account.a2p.messaging_campaign': { $exists: 0 },
},
],
},
{
$or: [
{ 'twilio_account.a2p.low_volume_standard_brand.status': 'PENDING' },
{
'twilio_account.a2p.low_volume_standard_brand.status': 'APPROVED',
'twilio_account.a2p.low_volume_standard_brand.identityStatus': 'UNVERIFIED',
},
{
'twilio_account.a2p.low_volume_standard_brand.status': 'APPROVED',
'twilio_account.a2p.low_volume_standard_brand.identityStatus': 'VERIFIED',
'twilio_account.a2p.messaging_service': { $exists: 0 },
'twilio_account.a2p.messaging_campaign': { $exists: 0 },
},
{
'twilio_account.a2p.low_volume_standard_brand.status': 'APPROVED',
'twilio_account.a2p.low_volume_standard_brand.identityStatus': 'VERIFIED',
'twilio_account.a2p.messaging_service': { $exists: 1 },
'twilio_account.a2p.messaging_campaign': { $exists: 0 },
},
],
},
],
},
},
{
$project: {
twilio_account: 1,
},
},
]);

if (accounts.length) {
let queue = await brandStatusCheck.start();
await Promise.all(
accounts.map(async a => {
try {
await queue.add(
{
account: a,
},
{
attempts: 5,
backoff: {
type: 'exponential',
delay: 60000,
},
removeOnComplete: true,
},
);
} catch (err) {
logger.error({ initiator: 'QM/communication/a2p/brand-status/add-queue', error: err });
}
}),
);
}
} catch (err) {
logger.error({ initiator: 'QM/communication/a2p/brand-status/service', error: err });
}
};

Query Breakdown:

Matches 4 scenarios per brand type:

  1. Pending: Brand registration submitted, awaiting Twilio review
  2. Approved but Unverified: Brand approved but identity not verified
  3. Verified, No Infrastructure: Brand verified but messaging service/campaign not created
  4. Service Created, No Campaign: Messaging service exists but campaign missing

3. Queue Processing (THE BRAND VERIFICATION LOGIC)โ€‹

File: queue-manager/queues/communication/a2p/brand.js

Purpose: Check brand status and automatically create messaging infrastructure

Key Functions:

  • Determine brand type (sole proprietor vs low volume standard)
  • Fetch latest brand status from Twilio
  • Create messaging service for verified brands
  • Create messaging campaign with compliance metadata
  • Handle different requirements for sole proprietor vs standard brands

Complete Processor:

const mongoose = require('mongoose');
const QueueWrapper = require('../../../common/queue-wrapper');
const logger = require('../../../utilities/logger');
const a2p = require('../twilio/services/a2p');

const processCb = async (job, done) => {
try {
const { account } = job.data;

const metadata = {
type: account.twilio_account.a2p.sole_proprietor_brand
? 'sole_proprietor_brand'
: 'low_volume_standard_brand',
twilio_creds: {
sid: account.twilio_account?.sid,
token: account.twilio_account?.authToken,
},
};

// Fetch latest brand status from Twilio
const brand = await a2p.getBrandStatus(
metadata,
account.twilio_account.a2p.sole_proprietor_brand?.sid ||
account.twilio_account.a2p.low_volume_standard_brand?.sid,
);

if (brand.status == 'APPROVED' && brand.identityStatus == 'VERIFIED') {
/**
* START: Create messaging service
*/
let service = account.twilio_account.a2p.messaging_service;

if (!service) {
service = await a2p.createMessagingService(metadata, {
friendlyName: `${account.twilio_account.a2p.submission_data.business.name} A2P Messaging Service`,
useInboundWebhookOnNumber: true,
});
}
/**
* END: Create messaging service
*/

/**
* START: Create messaging campaign
*/
const campaignBody = {
description: account.twilio_account.a2p.submission_data.campaign.use_case_description,
messageFlow: account.twilio_account.a2p.submission_data.campaign.end_user.consent,
messageSamples: account.twilio_account.a2p.submission_data.campaign.sample_messages,
usAppToPersonUsecase:
metadata.type == 'sole_proprietor_brand'
? 'SOLE_PROPRIETOR'
: account.twilio_account.a2p.submission_data.campaign.use_case,
hasEmbeddedLinks:
account.twilio_account.a2p.submission_data.campaign.messages_include_links,
hasEmbeddedPhone:
account.twilio_account.a2p.submission_data.campaign.messages_include_phone_numbers,
brandRegistrationSid: account.twilio_account.a2p[metadata.type].sid,
};

if (metadata.type == 'low_volume_standard_brand') {
campaignBody.optInKeywords =
account.twilio_account.a2p.submission_data.campaign.end_user.optin_keywords
.split(' ')
?.map(s => s.replace(/[^a-zA-Z0-9]/g, ''));
campaignBody.optInMessage =
account.twilio_account.a2p.submission_data.campaign.end_user.optin_message;
}

const campaign = await a2p.createMessagingCampaign(metadata, service.sid, campaignBody);
/**
* END: Create messaging campaign
*/
}

return done();
} catch (err) {
done(err);
}
};

const completedCb = async job => {};

let queue;

exports.start = async () => {
try {
if (!queue) queue = QueueWrapper(`comm_a2p_brandCheck`, 'global', { processCb, completedCb });
return Promise.resolve(queue);
} catch (err) {
logger.error({
initiator: 'QM/communication/a2p/brand-check/queue',
error: err,
message: `Error while starting queue`,
});
}
};

4. Twilio A2P Service Utilitiesโ€‹

File: queue-manager/queues/communication/twilio/services/a2p.js

Purpose: Wrapper functions for Twilio A2P API calls with database updates

Key Functions:

  1. getBrandStatus: Fetch brand registration status
  2. createMessagingService: Create messaging service for sending SMS
  3. createMessagingCampaign: Create A2P campaign linked to brand
  4. deleteMessagingCampaign: Remove campaign and A2P data

Example Functions:

const getBrandStatus = async (metadata, id) => {
const client = Twilio(metadata.twilio_creds.sid, metadata.twilio_creds.token);
const brand = await client.messaging.v1.brandRegistrations(id).fetch();
await Account.findOneAndUpdate(
{
'twilio_account.sid': metadata.twilio_creds.sid,
},
{
$set: {
[`twilio_account.a2p.${metadata.type}`]: JSON.parse(JSON.stringify(brand)),
},
},
);
return brand;
};

const createMessagingService = async (metadata, data) => {
const client = Twilio(metadata.twilio_creds.sid, metadata.twilio_creds.token);
const service = await client.messaging.v1.services.create(data);
await Account.findOneAndUpdate(
{
'twilio_account.sid': metadata.twilio_creds.sid,
},
{
$set: {
'twilio_account.a2p.messaging_service': JSON.parse(JSON.stringify(service)),
},
},
);
return service;
};

const createMessagingCampaign = async (metadata, service, data) => {
const client = Twilio(metadata.twilio_creds.sid, metadata.twilio_creds.token);
const campaign = await client.messaging.v1.services(service).usAppToPerson.create(data);
await Account.findOneAndUpdate(
{
'twilio_account.sid': metadata.twilio_creds.sid,
},
{
$set: {
'twilio_account.a2p.messaging_campaign': JSON.parse(JSON.stringify(campaign)),
},
},
);
return campaign;
};

๐Ÿ—„๏ธ Collections Usedโ€‹

_accountsโ€‹

  • Operations: Aggregate, Update
  • Model: shared/models/account.js
  • Usage Context: Store A2P brand registration and campaign data

Key Fields (twilio_account.a2p object):

  • sole_proprietor_brand: Object - sole proprietor brand data
    • sid: Brand registration SID
    • status: 'PENDING' | 'APPROVED' | 'REJECTED'
    • identityStatus: 'UNVERIFIED' | 'VERIFIED'
  • low_volume_standard_brand: Object - standard brand data
    • sid: Brand registration SID
    • status: 'PENDING' | 'APPROVED' | 'REJECTED'
    • identityStatus: 'UNVERIFIED' | 'VERIFIED'
  • messaging_service: Object - Twilio messaging service
    • sid: Service SID
    • friendlyName: Display name
    • useInboundWebhookOnNumber: Boolean
  • messaging_campaign: Object - A2P campaign
    • sid: Campaign SID
    • campaignStatus: 'IN_PROGRESS' | 'VERIFIED' | 'REJECTED'
    • usAppToPersonUsecase: Use case type
  • submission_data: Object - Original submission data
    • business: Business information
      • name: Business name
    • campaign: Campaign details
      • use_case: Use case type
      • use_case_description: Description
      • messages_include_links: Boolean
      • messages_include_phone_numbers: Boolean
      • sample_messages: Array of sample messages
      • end_user: Opt-in/out configuration
        • consent: Message flow description
        • optin_keywords: Keywords for opt-in
        • optin_message: Opt-in confirmation message

๐Ÿ”ง Job Configurationโ€‹

Cron Scheduleโ€‹

'* * * * *'; // Every minute

Frequency: 60 times per hour

Rationale: Frequent checks ensure quick campaign provisioning after brand approval.

Queue Settingsโ€‹

QueueWrapper(`comm_a2p_brandCheck`, 'global', {
processCb,
completedCb,
});

Queue Name: comm_a2p_brandCheck

Concurrency: Default (1)

Job Options:

{
attempts: 5,
backoff: {
type: "exponential",
delay: 60000 // Start with 60-second delay
},
removeOnComplete: true
}

Exponential Backoff: 60s, 120s, 240s, 480s, 960s (up to 16 minutes total)

๐Ÿ“‹ Processing Logic - Detailed Flowโ€‹

1. Brand Type Determinationโ€‹

Logic:

const metadata = {
type: account.twilio_account.a2p.sole_proprietor_brand
? 'sole_proprietor_brand'
: 'low_volume_standard_brand',
twilio_creds: {
sid: account.twilio_account?.sid,
token: account.twilio_account?.authToken,
},
};

Brand Types:

  • Sole Proprietor: Individual/freelancer business (simplified requirements)
  • Low Volume Standard: Standard business (< 6,000 SMS/day, requires opt-in keywords)

2. Brand Status Checkโ€‹

Twilio API Call:

GET https://messaging.twilio.com/v1/BrandRegistrations/{sid}

Response Fields:

  • status: Brand approval status
  • identityStatus: Identity verification status
  • sid: Brand registration SID
  • Other brand metadata

Database Update:

await Account.findOneAndUpdate(
{ 'twilio_account.sid': metadata.twilio_creds.sid },
{
$set: {
'twilio_account.a2p.sole_proprietor_brand': brand,
// OR
'twilio_account.a2p.low_volume_standard_brand': brand,
},
},
);

3. Messaging Service Creationโ€‹

Condition: Brand status == 'APPROVED' AND identityStatus == 'VERIFIED'

Twilio API Call:

POST https://messaging.twilio.com/v1/Services
{
"friendlyName": "Acme Corp A2P Messaging Service",
"useInboundWebhookOnNumber": true
}

Response:

  • sid: Service SID (used for campaign creation)
  • friendlyName: Display name
  • Other service metadata

Database Update:

await Account.findOneAndUpdate(
{ 'twilio_account.sid': metadata.twilio_creds.sid },
{
$set: {
'twilio_account.a2p.messaging_service': service,
},
},
);

4. Messaging Campaign Creationโ€‹

Campaign Body Construction:

Common Fields (both brand types):

{
description: "Campaign description",
messageFlow: "Consent/opt-in process description",
messageSamples: ["Sample message 1", "Sample message 2"],
usAppToPersonUsecase: "SOLE_PROPRIETOR" | "MIXED",
hasEmbeddedLinks: true,
hasEmbeddedPhone: false,
brandRegistrationSid: "BNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

Additional Fields (low volume standard only):

{
optInKeywords: ["START", "YES", "SUBSCRIBE"],
optInMessage: "Reply START to subscribe to our messages"
}

Opt-In Keywords Processing:

account.twilio_account.a2p.submission_data.campaign.end_user.optin_keywords
.split(' ')
?.map(s => s.replace(/[^a-zA-Z0-9]/g, ''));

Example: "START YES subscribe" โ†’ ["START", "YES", "subscribe"]

Twilio API Call:

POST https://messaging.twilio.com/v1/Services/{serviceSid}/UsAppToPerson
{
// Campaign body
}

Response:

  • sid: Campaign SID
  • campaignStatus: 'IN_PROGRESS' (initial status)
  • Other campaign metadata

Database Update:

await Account.findOneAndUpdate(
{ 'twilio_account.sid': metadata.twilio_creds.sid },
{
$set: {
'twilio_account.a2p.messaging_campaign': campaign,
},
},
);

5. Use Case Mappingโ€‹

Sole Proprietor:

  • Always uses 'SOLE_PROPRIETOR' use case
  • Simplified compliance requirements
  • No opt-in keywords required

Low Volume Standard:

  • Uses submitted use case from submission_data.campaign.use_case
  • Common values: 'MIXED', 'MARKETING', 'ACCOUNT_NOTIFICATIONS', etc.
  • Requires opt-in keywords and message

๐Ÿšจ Error Handlingโ€‹

Common Error Scenariosโ€‹

Brand Not Approvedโ€‹

Scenario: Brand status is 'PENDING' or 'REJECTED'

Handling: Job completes without creating messaging infrastructure, retry on next cron run

Impact: Account continues polling until approved

Identity Not Verifiedโ€‹

Scenario: Brand approved but identityStatus == 'UNVERIFIED'

Handling: Job completes without creating messaging infrastructure

Impact: Account waits for Twilio to complete identity verification

Messaging Service Creation Failureโ€‹

Scenario: Twilio API error (rate limit, invalid credentials, etc.)

Handling: Error thrown, job retries with exponential backoff (5 attempts)

Impact: Messaging service not created, campaign creation skipped

Campaign Creation Failureโ€‹

Scenario: Invalid campaign data, missing required fields, API error

Handling: Error thrown, job retries

Impact: Messaging service created but campaign missing (resolved on retry)

Missing Submission Dataโ€‹

Scenario: submission_data field missing or incomplete

Handling: Error thrown, job fails after retries

Impact: Cannot create campaign (manual intervention required)

Failed Job Callbackโ€‹

Note: No explicit failedCb defined - relies on default Bull error handling.

Completed Job Callbackโ€‹

const completedCb = async job => {};

Action: No-op (empty function)

๐Ÿ“Š Monitoring & Loggingโ€‹

Success Loggingโ€‹

Service Level:

  • No explicit success logging

Queue Level:

  • No explicit success logging

Error Loggingโ€‹

Cron Level:

  • Error in cron initialization

Service Level:

  • Error aggregating accounts
  • Error queuing individual accounts

Queue Level:

  • Error starting queue

Performance Metricsโ€‹

  • Brand Status Check: 1-2 seconds (Twilio API call)
  • Service Creation: 1-2 seconds (Twilio API call)
  • Campaign Creation: 2-5 seconds (Twilio API call)
  • Total Job Time: 5-10 seconds per account

๐Ÿ”— Integration Pointsโ€‹

Triggers This Jobโ€‹

  • Cron Schedule: Every minute (no external triggers)
  • Brand Approval: Twilio approves brand via external process

External Dependenciesโ€‹

  • Twilio A2P API: Brand registration, messaging services, campaigns
  • Twilio Credentials: Stored in twilio_account.sid and twilio_account.authToken

Jobs That Depend On Thisโ€‹

  • Campaign Status Check: Monitors campaign approval
  • Number Assignment: Assigns phone numbers to verified campaigns
  • A2P Registration Form: Internal API creates initial brand registration
  • SMS Sending: Requires verified campaign for 10DLC compliance

โš ๏ธ Important Notesโ€‹

Side Effectsโ€‹

  • โš ๏ธ Messaging Service Creation: Creates billable Twilio service
  • โš ๏ธ Campaign Creation: Submits campaign for review (may take hours/days)
  • โš ๏ธ Database Updates: Modifies twilio_account.a2p object

Performance Considerationsโ€‹

  • Sequential Processing: One account at a time prevents Twilio rate limiting
  • Exponential Backoff: 60-second initial delay prevents rapid retries
  • Job Cleanup: removeOnComplete prevents queue bloat

Business Logicโ€‹

Why Every Minute?

  • Brand approvals can happen any time
  • Quick provisioning improves customer experience
  • Campaigns can take days for approval, but service should be ready

Why Two Brand Types?

  • Twilio differentiates sole proprietors (simpler process)
  • Standard brands have more compliance requirements
  • Different opt-in/out rules apply

Why Automatic Provisioning?

  • Reduces manual work for brand-to-campaign setup
  • Ensures compliance requirements met immediately
  • Customers can start sending SMS as soon as approved

Why Exponential Backoff?

  • Twilio API rate limits apply
  • Temporary errors (network, API downtime) resolve quickly
  • Persistent errors (bad data) don't spam logs

Maintenance Notesโ€‹

  • Brand Types: Two types hardcoded (sole_proprietor, low_volume_standard)
  • Use Cases: Mapped from submission data (varies by type)
  • Opt-In Keywords: Sanitized with regex /[^a-zA-Z0-9]/g (alphanumeric only)
  • Service Naming: Uses business name from submission data
  • Job Cleanup: removeOnComplete: true prevents indefinite retention

Code Quality Issuesโ€‹

Issue 1: Brand SID Retrieval

account.twilio_account.a2p.sole_proprietor_brand?.sid ||
account.twilio_account.a2p.low_volume_standard_brand?.sid;

Issue: Uses fallback instead of metadata.type-based lookup.

Suggestion: Use consistent lookup:

account.twilio_account.a2p[metadata.type]?.sid;

Issue 2: JSON.parse(JSON.stringify())

JSON.parse(JSON.stringify(brand));

Issue: Used to convert Twilio SDK object to plain object, but inefficient.

Suggestion: Use object destructuring or Object.assign({}, brand).

Issue 3: No Validation of Submission Data

account.twilio_account.a2p.submission_data.campaign.use_case_description;

Issue: No null checks, could throw error if submission_data incomplete.

Suggestion: Add validation before API calls.

๐Ÿงช Testingโ€‹

Manual Triggerโ€‹

# Via API (if QM_HOOKS=true)
POST http://localhost:6002/api/trigger/communication/a2p/brand

Simulate Brand Approvalโ€‹

const Account = require('./models/account');

// Set brand to APPROVED + VERIFIED (but no campaign)
await Account.findByIdAndUpdate(accountId, {
'twilio_account.a2p.sole_proprietor_brand': {
sid: 'BNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
status: 'APPROVED',
identityStatus: 'VERIFIED',
},
'twilio_account.a2p.submission_data': {
business: {
name: 'Acme Corp',
},
campaign: {
use_case_description: 'Customer notifications',
end_user: {
consent: 'Users opt-in via website',
},
sample_messages: ['Your order has shipped'],
messages_include_links: false,
messages_include_phone_numbers: false,
},
},
});

console.log('Brand approved, waiting for messaging service/campaign creation');

Verify Campaign Creationโ€‹

// Wait for next cron run (up to 1 minute)
await new Promise(resolve => setTimeout(resolve, 65000));

// Check account for messaging infrastructure
const account = await Account.findById(accountId);
console.log('Messaging service created:', !!account.twilio_account.a2p.messaging_service);
console.log('Campaign created:', !!account.twilio_account.a2p.messaging_campaign);
console.log('Campaign status:', account.twilio_account.a2p.messaging_campaign?.campaignStatus);

Test Low Volume Standard Brandโ€‹

// Set standard brand (requires opt-in keywords)
await Account.findByIdAndUpdate(accountId, {
'twilio_account.a2p.low_volume_standard_brand': {
sid: 'BNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
status: 'APPROVED',
identityStatus: 'VERIFIED',
},
'twilio_account.a2p.submission_data': {
business: {
name: 'Acme Corp',
},
campaign: {
use_case: 'MIXED',
use_case_description: 'Marketing and notifications',
end_user: {
consent: 'Users opt-in via website',
optin_keywords: 'START YES subscribe',
optin_message: 'Reply START to subscribe',
},
sample_messages: ['Special offer today!'],
messages_include_links: true,
messages_include_phone_numbers: false,
},
},
});

// Verify opt-in keywords sanitized
await new Promise(resolve => setTimeout(resolve, 65000));
const account = await Account.findById(accountId);
console.log('Opt-in keywords:', account.twilio_account.a2p.messaging_campaign?.optInKeywords);
// Expected: ["START", "YES", "subscribe"]

Monitor Queue Processingโ€‹

# Watch logs during brand checking
tail -f logs/queue-manager.log | grep "a2p"

# Expected outputs (in Twilio service functions):
# (No explicit logging in current implementation)

Job Type: Scheduled with Account-Level Queuing
Execution Frequency: Every minute
Average Duration: 5-10 seconds per account
Status: Active

๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM