Skip to main content

Campaigns Service


### getAddedTrackingNumbers(req, res, next)// Success - 200

{

**Purpose**: Retrieve list of CallRail tracking numbers already added to campaigns success: true,

message: "SUCCESS",

**Parameters**: data: [

- `req.auth.account_id` (ObjectId) - Account context {

- `req.auth.uid` (ObjectId) - Campaign owner _id: ObjectId,

- `req.query.type` (String, optional) - Filter type campaign_name: "Summer Lead Gen Campaign",

status: false,

**Returns**: selected_user: [...],

```javascript updated_at: Date

{ }

success: true, ]

message: 'SUCCESS',}

data: [```

{

tracking_number: String,**Campaign Disconnection Notification**:

campaign_id: ObjectId,

campaign_name: String```mermaid

}sequenceDiagram

] participant Client

} participant API

``` participant MongoDB

participant FCM

**Business Logic Flow**: participant Email

participant Socket

1. **Build Query**

```javascript Client->>API: PUT /campaigns/:id (status: false)

let campaignData = { API->>MongoDB: Update campaign status

account_id: accountId.toString(), MongoDB-->>API: Campaign updated

owner: owner.toString(),

is_deleted: false alt Campaign disabled or deleted

}; API->>FCM: Push notification

``` API->>Email: Email notification

Note right of Email: Subject: Inbound Campaign Disconnected

2. **Get Tracking Numbers** end

```javascript

let trackingNumbers = await campaignDataModel.getAllAddedTrackingNumbers({ API->>Socket: Emit campaigncountupdated

campaignData: campaignData, API-->>Client: 200 Success

type: type,```

});

```**MongoDB Operations**:

- Model filters campaigns with tracking_number field

- Returns array of tracking numbers with campaign info| Operation | Collection | Query | Purpose |

| ------------ | --------------- | ----------- | ------------------- |

3. **Return List**| `update` | `campaign-data` | `{_id}` | Update campaign |

- Used for validation when creating new CallRail campaigns| `getFilters` | `campaign-data` | Aggregation | Get updated filters |

- Prevents duplicate tracking number entry

**Automatic Notifications**:

**Key Business Rules**:When campaign is disabled (`status: false`) or deleted (`is_deleted: true`):

- **Active Campaigns Only**: Excludes soft-deleted campaigns

- **Owner Scoping**: Only returns owner's tracking numbers#### 1. Push Notification (FCM)

- **Validation Support**: Used by UI to show already-used numbers

```javascript

**Example Usage**:{

```javascript title: "Inbound Campaign Disconnected.",

const numbers = await getAddedTrackingNumbers({ body: "Your {campaign_name} integration/webhook has disconnected from your Inbound app. We recommend reconnecting the integration/webhook to avoid any disruption in service.",

auth: { account_id, uid }, data: {

query: {} campaign_id: String

}); },

// Returns list of tracking numbers to validate against module: "inbound",

``` type: "campaign_disconnected"

}

**Side Effects**: None (read-only)```



---#### 2. Email Notification



### updateCampaign(req, res, next)```javascript

{

**Purpose**: Update existing campaign configuration with disconnection notifications subject: "Inbound Campaign Disconnected.",

content: [

**Parameters**: {

- `req.auth.account_id` (ObjectId) - Account context value: "Your {campaign_name} integration/webhook has disconnected..."

- `req.auth.uid` (ObjectId) - Owner ID }

- `req.params.campaignid` (ObjectId) - Campaign to update ],

- `req.body.campaigndata` (Object) - Fields to update origin: "campaign_disconnected"

}

**Returns**:```

```javascript

{**Socket.IO Events**:

success: true,

message: 'SUCCESS',```javascript

data: [Campaign]// Emitted to campaign owner with updated filters

}utilities.socketEmit('campaigncountupdated', [owner.toString()], filtersData);

Business Logic Flow:Business Rules:

  1. Extract Parameters1. Selected Users: IDs converted to ObjectIds before saving


    const accountId = req.auth.account_id;3. **Notification Trigger**: Only sends notifications when `status` changes to `false` or `is_deleted` becomes `true`

    const owner = req.auth.uid;4. **Error Logging**: Notification failures logged but don't block update operation

    let campaignId = req.params.campaignid;

    let campaignData = req.body.campaigndata;---

Data Modelsโ€‹

  1. Convert Selected Users to ObjectId


    if (campaignData.selected_user) {

    let suser = [];```javascript

    for (let user of campaignData.selected_user) {{

    user.id = new mongoose.Types.ObjectId(user.id); _id: ObjectId,

    suser.push(user); campaign_name: String, // Campaign display name

    } integration: String, // Integration type (see list above)

    campaignData.selected_user = suser; account_id: ObjectId, // DashClicks account

    } owner: ObjectId, // Campaign creator/owner

// External IDs

  1. Handle Currency Updates page_id: String, // Facebook page ID, form ID, etc.


    if (campaignData.currency) { page_url: String, // Landing page URL (trailing slash removed)

    campaignData.currency = campaignData?.currency; tracking_number: String, // Phone tracking number (CallRail/CTM)

    }

    ``` // InstaForms Integration

inhouse_form_id: ObjectId, // Generated form ID

  1. Update Campaign inhouse_form_secret_id: String, // UUID for form access


    let campaignupdate = await campaignDataModel.update( // Representative Assignment

    { _id: campaignId }, selected_user: [

    campaignData {

    ); id: ObjectId, // User ID

    ``` name: String, // User display name

    - Returns updated campaign with populated fields custom_number: String // Custom phone for SMS (optional)

    }

  2. Refresh Filters and Emit Socket ],


    let filtersData = await campaignDataModel.getFilters({ // Notification Configuration

    accountId: accountId.toString(), notification_type: String, // "only_send_email" | "only_send_sms" | "send_email_sms"

    owner: owner.toString(), email_details: {

    req: req, subject: String,

    }); message: String, // Supports template variables

    from: String,

    utilities.socketEmit('campaigncountupdated', [owner.toString()], filtersData); attachments: Array,

    ``` reply_to: {

    email: String,

  3. Send Disconnection Notifications name: String


    if (campaignData?.status == false || campaignData?.is_deleted == true) { },

    if (campaignupdate?.[0]?.campaign_name) { sms_details: {

    // FCM Push Notification message: String, // Supports template variables

    let notification = { from: String // Twilio phone number

    title: `Inbound Campaign Disconnected.`, },

    body: `Your ${campaignupdate[0].campaign_name} integration/webhook has disconnected from your Inbound app. We recommend reconnecting the integration/webhook to avoid any disruption in service.`,

    data: { // CRM Integration

    campaign_id: campaignupdate[0].id ? campaignupdate[0].id.toString() : null, add_to_crm: Boolean, // Auto-create contacts (default: true)

    }, add_person_or_business: String, // "person" | "business"

    module: 'inbound',

    type: 'campaign_disconnected', // Deal Automation

    }; create_deal: Boolean, // Auto-create deals (default: false)

    pipeline_id: ObjectId, // CRM pipeline for deals

    sendFCMNotification({ stage_id: ObjectId, // Initial pipeline stage

    req: req, deal_value: Number, // Default deal value

    notification: notification, currency: String, // Deal currency (e.g., "USD", "EUR")

    module: 'inbound',

    type: 'campaign_disconnected', // Status

    }); status: Boolean, // Active/inactive (default: true)

    is_deleted: Boolean, // Soft delete flag (default: false)

    // Email Notification

    let mailBody = { // Metadata

    subject: `Inbound Campaign Disconnected.`, created_at: Date,

    content: [ updated_at: Date

    {}

    value: `Your ${campaignupdate[0].campaign_name} integration/webhook has disconnected from your Inbound app. We recommend reconnecting the integration/webhook to avoid any disruption in service.`,```

    },

    ],---

    origin: 'campaign_disconnected',

    };## Integration Points



    sendMailNotification({### Internal Services

    req: req,

    mailBody: mailBody,- **CRM Module**: Contact and deal creation

    module: 'inbound',- **Forms Module**: InstaForms template and creation

    type: 'campaign_disconnected',- **Notification Service**: FCM push, email, SMS

    });- **Socket.IO**: Real-time campaign updates

    }

    }### External Services

    • Triggers when campaign disabled (status: false)- Facebook Graph API: Lead Ads integration

    • Triggers when campaign deleted (is_deleted: true)- Twilio: SMS notifications

    • Sends both push and email notifications- SendGrid: Email notifications

    • Errors logged but don't fail update

Utilitiesโ€‹

  1. Return Updated Campaign
  • AppendCopyToName: Ensures unique form names

Key Business Rules:- socketEmit: Real-time updates to frontend

  • Disconnection Alerts: Automatic notifications when campaigns disabled/deleted- checkAccountStatus: Validates account is active

  • Multi-Channel Notifications: Both FCM push and email sent

  • Error Tolerance: Notification failures logged but update succeeds---

  • Socket Updates: Real-time filter updates to connected clients

  • Currency Tracking: Supports currency field updates## Error Handling

Notification Trigger Conditions:### Common Errors

  • status: false - Campaign manually disabled

  • is_deleted: true - Campaign soft deleted| Error | Status | Message | Cause |

| ------------------------- | ------ | -------------------------------- | ----------------------------------------- |

Example Usage:| Duplicate tracking number | 409 | "Tracking number already added!" | Tracking number exists in active campaign |


// Update campaign settings| Invalid campaign | 404 | Campaign not found | Campaign doesn't exist or deleted |

await updateCampaign({

auth: { account_id, uid },---

params: { campaignid: 'campaign_id' },

body: {## Testing Recommendations

campaigndata: {

notification_type: 'send_email_sms',### Unit Tests

email_details: {

subject: 'New Lead',1. **saveCampaign**:

message: 'You have a new lead!',

from: 'leads@example.com' - Test InstaForms auto-creation

} - Test tracking number uniqueness validation

} - Test page URL normalization

} - Test selected_user ObjectId conversion

}); - Test socket emission



// Disable campaign (triggers notifications)2. **updateCampaign**:

await updateCampaign({

auth: { account_id, uid }, - Test status change notification trigger

params: { campaignid: 'campaign_id' }, - Test currency field support

body: { - Test socket emission with filters

campaigndata: {

status: false3. **getCampaignsInfo**:

}

} - Test role-based filtering

}); - Test pagination (page-based and cursor-based)

// Result: Campaign disabled, FCM and email notifications sent - Test search functionality

  1. getAddedTrackingNumbers:

Side Effects: - Test type filtering

  • Updates Campaign: Modifies campaign document - Test account isolation

  • Socket Emission: Real-time filter updates

  • Sends Notifications: FCM push and email on disconnection### Integration Tests

  • Logs Errors: Notification failures logged (non-blocking)

  • Full campaign lifecycle (create โ†’ update โ†’ disable)

---- Notification delivery on campaign disconnect

  • Socket event propagation

๐Ÿ”€ Integration Points- InstaForms template integrationโ€‹

Internal Services---โ€‹

  • Forms Service (link removed - file does not exist):## Performance Considerations

    • Creates in-house forms from templates

    • Generates form_secret_id for public access- Pagination: Use cursor-based for large datasets

    • Links forms to campaigns via inhouse_form_id- Indexing: Ensure indexes on account_id, tracking_number, integration, is_deleted

  • Socket Optimization: Only emit to campaign owner, not all users

  • Notifications Service (link removed - file does not exist):- Aggregation: Filter counts cached where possible

    • sendFCMNotification() - Push notifications for campaign events

    • sendMailNotification() - Email alerts for disconnections---

  • Socket Service (link removed - file does not exist):## Security Considerations

    • utilities.socketEmit() - Real-time campaign count updates

    • Event: 'campaigncountupdated' with filter data1. Multi-Tenant Isolation: All queries filtered by account_id

  1. Role-Based Access: Non-admins only see assigned campaigns

Utilities3. Tracking Number Validation: Prevents duplicate assignmentsโ€‹

  1. Soft Delete: Preserves historical data
  • AppendCopyToName (link removed - file does not exist):5. Notification Rate Limiting: Consider rate limiting for notification failures

    • Generates unique names with " (Copy)" suffix

    • Prevents duplicate campaign/form names---

    • Async promise-based interface

Last Updated: October 2025

  • UUID Generator (link removed - file does not exist):Status: Production Ready

    • Generates form_secret_id for public form accessComplexity: MEDIUM

    • v4 UUIDs for cryptographic randomness

External Dependenciesโ€‹

  • Mongoose Models:
    • campaignDataModel - Campaign CRUD operations
    • formsModel - Form creation and management
    • templatesModel - Form template lookup

๐Ÿงช Edge Cases & Special Handlingโ€‹

CallRail Tracking Number Edge Casesโ€‹

Duplicate Detection:

// Only checks active campaigns
let condition = {
tracking_number: '4155551234',
is_deleted: false,
};
// Soft-deleted campaigns excluded from uniqueness check

Null/Undefined Tracking Numbers:

  • Validation skipped if tracking_number not provided
  • Only CallRail integration requires tracking numbers

URL Normalization Edge Casesโ€‹

Multiple Trailing Slashes:

// Input: "https://example.com///"
// After trim: "https://example.com///"
// After slice: "https://example.com//"
// Issue: Only removes last slash

Query Parameters:

// Input: "https://example.com/page?param=value/"
// Output: "https://example.com/page?param=value"
// Correct: Handles query strings properly

Fragment Identifiers:

// Input: "https://example.com/page#section/"
// Output: "https://example.com/page#section"
// Correct: Handles hash fragments

Form Generation Edge Casesโ€‹

Template Not Found:

let getTemplate = await templatesModel.findOne({ form_name: 'InstaSites' });
if (!getTemplate?._id) {
// Form fields not set on campaign
// campaignData.inhouse_form_id remains undefined
// Campaign still created but without form
}

Form Name Conflicts:

  • AppendCopyToName adds " (Copy)", " (Copy 2)", etc.
  • Searches existing campaigns collection for name conflicts
  • Prevents duplicate form names within account

Missing Template Categories:

delete getTemplate.categories; // Remove before copying
delete getTemplate._id; // Remove template _id
// Prevents template data pollution in new forms

Representative Assignment Edge Casesโ€‹

Empty Selected Users Array:

campaignData.selected_user = [];
// Valid: Campaign has no assigned representatives
// Leads will have user_id: null

Invalid User IDs:

// Model validation catches invalid ObjectId references
// Error thrown during save: "inhouse_form_id validation failed"

Custom Number Handling:

{
id: 'user_id',
custom_number: null // Valid: Uses default SMS number
}
{
id: 'user_id',
custom_number: '+14155551234' // Override SMS number for this user
}

Notification Edge Casesโ€‹

Notification Failures:

try {
sendFCMNotification(...);
sendMailNotification(...);
} catch (error) {
logger.error({ error, message: 'Notification failed' });
// Error logged but update succeeds
// User still updated, notifications just failed
}

Multiple Status Changes:

// Update 1: status: false (sends notification)
// Update 2: status: true (no notification)
// Update 3: status: false (sends notification again)
// Each status change to false triggers new notifications

Deleted Campaign Notifications:

// is_deleted: true also triggers disconnection notification
// Prevents confusion when campaigns disappear from UI

โš ๏ธ Important Notesโ€‹

  1. Tracking Number Uniqueness: CallRail tracking numbers must be unique across all active campaigns in account. Soft-deleted campaigns excluded from check. Always validate before creating CallRail campaigns.

  2. URL Normalization: page_url automatically normalized by removing trailing slash. Only removes last slash - doesn't handle multiple slashes. URLs stored consistently for matching and display.

  3. Form Generation: InstaSite/InstaReport campaigns automatically create forms from templates. Form name generated with AppendCopyToName to avoid conflicts. form_secret_id is public UUID, not MongoDB _id.

  4. Representative Assignment: selected_user array must have ObjectId for id field. custom_number optional for SMS override. Empty array valid (no representatives assigned).

  5. Disconnection Notifications: Sent when status set to false OR is_deleted set to true. Both FCM push and email notifications sent. Notification failures logged but don't block update.

  6. Socket Updates: 'campaigncountupdated' event emitted to owner's socket on create/update. Filter data included in event. Enables real-time dashboard updates without polling.

  7. Role-Based Access: Non-admin users only see campaigns in selected_user array. Admin users see all account campaigns. Always apply role filter in queries.

  8. Currency Support: Optional currency field for multi-currency tracking. Used in deal creation to set deal currency. Defaults to account currency if not specified.

  9. Soft Delete Behavior: is_deleted: false filter applied to all queries. Soft-deleted campaigns excluded from tracking number uniqueness. Preserves historical data while hiding from UI.

  10. Form Template Lookup: Templates identified by form_name ('InstaSites' or 'InstaReport'). Template categories removed before copying. Template _id removed to create new document.

  • Leads Service - Lead capture and management
  • Webhooks Service - Webhook handling for integrations
  • Reports Service - Campaign analytics and reporting
  • Forms Service (link removed - file does not exist) - Form creation and management
  • Notifications Service (link removed - file does not exist) - Push and email notifications
  • Socket Utility (link removed - file does not exist) - Real-time updates
๐Ÿ’ฌ

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