Erpnext Zoho Integration

ERPNext and Zoho Integration App

Install Erpnext Zoho Integration

bench get-app https://github.com/Yaiphalemba/erpnext-zoho-integration

Add the Frappe Gems badge to your README

Maintain Erpnext Zoho Integration? Paste this into your README:

[![Listed on Frappe Gems](https://frappegems.com/api/method/frappe_gems.seo.badge?app=Yaiphalemba%2Ferpnext-zoho-integration)](https://frappegems.com/gems/apps/Yaiphalemba/erpnext-zoho-integration)

About Erpnext Zoho Integration

ERPNext Zoho Integration

Version: 0.0.1 | Author: Yanky | License: MIT

A comprehensive Frappe/ERPNext integration module that syncs Zoho Campaigns email campaign data directly into your ERPNext instance. Pull campaign metrics, recipient engagement data, and build analytics on top of your email marketing performance.


Table of Contents

  1. Overview
  2. Architecture
  3. Setup & Installation
  4. Core Concepts
  5. API Reference
  6. Data Models
  7. Sync Flow
  8. Authentication
  9. Troubleshooting

Overview

This module bridges Zoho Campaigns with ERPNext, enabling teams to:

  • Sync campaign metadata from Zoho (subject, sender, sent time, status, etc.)
  • Pull comprehensive analytics (opens, clicks, bounces, unsubscribes, engagement metrics)
  • Track recipient actions with granular data on who opened, clicked, or bounced
  • Auto-link contacts between systems based on email addresses
  • Run reports on campaign performance within ERPNext

Key Features

  • OAuth 2.0 authentication with automatic token refresh
  • Hourly automatic sync via Frappe scheduler
  • Bi-directional data mapping (campaign metadata ↔ analytics)
  • Support for multiple recipient action types (opens, clicks, bounces, complaints)
  • Clean UI dashboard showing live campaign metrics
  • Extensible architecture for adding custom fields and workflows

Architecture

Module Structure

erpnext_zoho_integration/
├── api/
│   ├── oauth.py              # OAuth 2.0 flow, token management
│   ├── campaigns.py          # Zoho Campaigns API wrapper
│   ├── sync.py               # Sync logic & data mapping
│   └── custom_fields.py      # Custom field definitions
├── doctype/
│   ├── zoho_settings/        # Settings doctype (OAuth credentials)
│   ├── campaign_analytics/   # Analytics table doctype
│   ├── campaign_recipient/   # Individual recipient records
│   └── ...
├── custom/
│   ├── campaign.json         # Custom fields added to Campaign doctype
│   └── contact.json          # Custom fields added to Contact doctype
├── public/js/
│   └── campaign.js           # Frontend dashboard & interactions
└── report/
    └── campaign_performance/ # Query report for analytics

Data Flow

Zoho Campaigns API
       ↓
oauth.py (token mgmt)
       ↓
campaigns.py (API calls)
       ↓
sync.py (data transformation)
       ↓
Campaign (doctype)
├── Campaign Analytics (child table)
├── Campaign Recipient (linked doctype)
└── Contact (linked doctype)

Key Design Patterns

Token Management: The get_valid_token() function automatically refreshes tokens before expiry (5-minute buffer). If a 401 response occurs, it triggers a refresh and retries once.

Generic API Wrapper: make_api_call() abstracts all HTTP calls to Zoho, handling headers, error responses, and status checking in one place.

Data Deduplication: Recipients are checked by (campaign, email, action_type) to prevent duplicates. Contacts are matched first by Zoho ID, then by email.

Error Resilience: All API calls are wrapped with try-catch, errors are logged to Frappe's error log, and users see friendly messages.


Setup & Installation

Prerequisites

  • ERPNext/Frappe v14+
  • Python 3.7+
  • requests library (included with Frappe)

Step 1: Install the App

cd ~/frappe-bench
bench get-app erpnext_zoho_integration https://github.com/yourusername/erpnext_zoho_integration.git
bench install-app erpnext_zoho_integration
bench migrate

Step 2: Register with Zoho

  1. Log in to Zoho Campaigns
  2. Go to API Console to add/register. Refer Zoho Campaings API Guide/Documentation and App/Client Registration Guide/Documentation for detailed guide and steps on how to get Client ID and Client Secret.
  3. Create a new OAuth app:
    • App Name: ERPNext Integration
    • Redirect URI: https://yourdomain.com/api/method/erpnext_zoho_integration.erpnext_zoho_integration.api.oauth.callback
    • Scopes: ZohoCampaigns.campaign.READ ZohoCampaigns.contact.CREATE ZohoCampaigns.contact.READ ZohoCampaigns.contact.UPDATE
  4. Copy your Client ID and Client Secret

Step 3: Configure in ERPNext

  1. Go to Zoho Settings in your ERPNext instance
  2. Fill in:
    • Client ID: From step 2
    • Client Secret: From step 2
    • Redirect URI: https://yourdomain.com/api/method/erpnext_zoho_integration.erpnext_zoho_integration.api.oauth.callback
  3. Click Authorize with Zoho → You'll be redirected to Zoho's OAuth screen
  4. After granting permissions, you'll return with an authorization Code
  5. Back in Zoho Settings, click Fetch Tokens
  6. Click Test Connection to verify

Step 4: Initial Sync

Once authenticated, click Sync All Campaigns in Zoho Settings. This will: - Fetch all sent campaigns from Zoho - Create/update Campaign records in ERPNext - Sync analytics and recipient data - Create/link Contact records


Core Concepts

Campaigns

A Campaign in this integration is an email campaign sent via Zoho Campaigns. The custom fields store: - zoho_campaign_id / zoho_campaign_key: Zoho identifiers - zoho_subject, zoho_from_email, zoho_reply_to: Email metadata - zoho_sent_time, zoho_campaign_status: Temporal data - campaign_analytics: Child table with metrics (opens, clicks, bounces, etc.)

Campaign Analytics

A child table storing individual metrics as key-value pairs: - metric: The metric name (e.g., "Opens", "Click Rate %") - value: The numeric value - percentage: For percentage-based metrics

Campaign Recipients

Represents individual recipient actions within a campaign. Each record tracks: - Which Campaign and Contact - Action Type: Sent, Opened, Clicked, Hard Bounced, Soft Bounced, Unsubscribed, Complaint - Action Date: When the action occurred - Location: Country, city, state - Additional Data: Open/click reports (JSON), URL clicks, full name, job title, etc.

Zoho Settings

A singleton doctype storing OAuth credentials: - client_id, client_secret: OAuth credentials - access_token, refresh_token: Current tokens - token_expiry: When access token expires - is_active: Boolean flag indicating if integration is active - api_domain: Zoho's API endpoint


API Reference

OAuth Module (api/oauth.py)

authorize()

Redirects user to Zoho's OAuth consent screen. - Whitelist: Yes (guest allowed) - Returns: Redirect to Zoho auth URL

fetch_tokens(code)

Exchanges OAuth authorization code for access & refresh tokens. - Args: code (authorization code from callback) - Whitelist: Yes (guest allowed) - Returns: Token response with access_token, refresh_token, expires_in, api_domain - Side Effects: Saves tokens to Zoho Settings

refresh_access_token()

Refreshes an expired access token using the refresh token. - Whitelist: Yes (guest allowed) - Returns: New access_token - Throws: If no refresh token exists


Campaigns Module (api/campaigns.py)

get_valid_token()

Returns a valid access token, refreshing if expired. - Args: None - Returns: Access token string - Throws: If integration not active

make_api_call(endpoint, method="GET", params=None, data=None)

Generic wrapper for all Zoho API calls. - Args: - endpoint: Zoho API endpoint (e.g., "recentcampaigns") - method: HTTP method ("GET" or "POST") - params: Query parameters dict - data: JSON payload dict - Returns: Parsed JSON response - Behavior: Auto-retries once on 401 with token refresh

get_recent_campaigns(limit=20)

Fetches recent campaigns from Zoho. - Whitelist: Yes - Args: limit (max records to fetch) - Returns: {"campaigns": [...], "total_count": int, "fetched_count": int}

get_campaign_report(campaign_key)

Fetches comprehensive report for a single campaign. - Whitelist: Yes - Args: campaign_key (Zoho campaign key) - Returns: Nested dict with campaign details, reports, reach, location data

get_campaign_recipients(campaign_key, action="openedcontacts", fromindex=1, range_val=20)

Fetches recipients grouped by action type. - Whitelist: Yes - Args: - campaign_key: Zoho campaign key - action: One of openedcontacts, clickedcontacts, bouncedcontacts, senthardbounce, sentsoftbounce, optoutcontacts, spamcontacts - fromindex: Pagination start (1-indexed) - range_val: Records per page (max 100 recommended) - Returns: {"recipients": [...], "action": str, "total_fetched": int}

sync_campaign_data(campaign_key)

Convenience function fetching and structuring all campaign data. - Whitelist: Yes - Args: campaign_key - Returns: Nested dict with report, opened_contacts, clicked_contacts, bounced_contacts, unsubscribed_contacts


Sync Module (api/sync.py)

sync_all_campaigns()

Hourly scheduled sync of all recent sent campaigns. - Whitelist: Yes - Returns: {"success": bool, "synced_count": int, "total_campaigns": int, "errors": [...]} - Behavior: Only syncs campaigns with status "Sent"

sync_single_campaign(campaign_data)

Internal function syncing a single campaign. - Args: Campaign dict from Zoho API - Returns: Created/updated Campaign doc - Side Effects: Creates Campaign record, syncs analytics, syncs recipients

sync_campaign_analytics(campaign, campaign_key)

Syncs metrics from Zoho report into Campaign Analytics child table. - Args: Campaign doc, Zoho campaign key - Side Effects: Clears existing analytics, inserts new ones

sync_campaign_recipients_data(campaign, campaign_key)

Syncs all recipient actions (opens, clicks, bounces, etc.). - Args: Campaign doc, Zoho campaign key - Behavior: Handles multiple action types, includes debug logging

sync_recipient(campaign, recipient_data, action_type)

Syncs individual recipient record with detailed action data. - Args: Campaign doc, recipient dict from Zoho, action type string - Side Effects: Creates/updates Campaign Recipient, links to Contact

find_or_create_contact(contact_data)

Finds or creates an ERPNext Contact based on email/Zoho ID. - Args: Recipient contact data dict - Returns: Contact doc or None - Matching Logic: Zoho ID first, then email

update_contact_from_zoho(contact, contact_data)

Updates Contact doc with Zoho metadata. - Args: Contact doc, Zoho contact data - Side Effects: Sets company, designation, Zoho ID, status

sync_campaign_by_name(campaign_name)

Manual sync of a specific campaign. - Whitelist: Yes - Args: campaign_name (ERPNext Campaign name) - Returns: {"success": bool, "message": str}


Data Models

Campaign (Extended)

Standard ERPNext Campaign doctype with additional custom fields:

Zoho Campaign Data Section: - zoho_campaign_id (Data, unique, read-only) - zoho_campaign_key (Data, read-only) - zoho_subject (Data, read-only) - zoho_from_email (Data, read-only) - zoho_sent_time (Datetime, read-only) - zoho_campaign_status (Data, read-only) — e.g., "Sent", "Draft" - zoho_campaign_type (Data, read-only) - zoho_reply_to (Data, read-only) - zoho_preview_url (Small Text, read-only) — Direct link to preview

Campaign Analytics Section: - campaign_analytics (Table, Campaign Analytics doctype) - last_synced (Datetime, read-only)

Campaign Analytics (Child Table)

Fields: - metric (Data, required) — Metric name (e.g., "Opens") - value (Data) — Numeric value - percentage (

Related Integrations apps for Frappe & ERPNext

  • Insights — Open Source Business Intelligence Tool
  • Raven — Simple, open source team messaging platform
  • Frappe Whatsapp — WhatsApp cloud integration for frappe
  • Frappe Assistant Core — Infrastructure that connects LLMs to ERPNext. Frappe Assistant Core works with the Model Context Protocol (MCP) to expose ERPNext functionality to any compatible Language Model
  • Biometric Attendance Sync Tool — A simple tool for syncing Biometric Attendance data with your ERPNext server
  • Frappe React Sdk — React hooks for Frappe
  • Frappe Js Sdk — TypeScript/JavaScript library for Frappe REST API
  • Mcp — Frappe MCP allows Frappe apps to function as MCP servers