Thena to Podium Migration: The CTO's Technical Guide
Migrate from Thena to Podium with this technical guide covering data model translation, API constraints, object mapping, and step-by-step ETL architecture.
Planning a migration?
Get a free 30-min call with our engineers. We'll review your setup and map out a custom migration plan — no obligation.
Schedule a free call- 1,500+ migrations completed
- Zero downtime guaranteed
- Transparent, fixed pricing
- Project success responsibility
- Post-migration support included
Thena to Podium Migration: The CTO's Technical Guide
Last verified: July 2025. Thena API v2 endpoints validated against docs.thena.ai; Podium API v4 endpoints validated against docs.podium.com. If you are reading this after January 2026, re-check rate limits and endpoint availability — both vendors update quarterly.
TL;DR: Thena → Podium Migration
Thena is an AI-native B2B helpdesk built around Slack, MS Teams, email, and live chat. Podium is a local-business customer interaction platform centered on SMS messaging, webchat, reviews, and payments. They share almost no structural overlap. Thena Tickets become Podium Conversations (with significant context loss). Thena Contacts map to Podium Contacts. Thena Accounts have no direct Podium equivalent — flatten them to Contact Tags or Attributes. Knowledge base articles, workflows, SLAs, and AI agent configurations do not transfer. There is no native migration path. A custom API-based ETL pipeline is the only reliable approach for full-fidelity migration. Budget 80–160 engineer-hours for a mid-size dataset (10K–50K tickets) — broken down as: extraction ~20 hrs, transform logic ~40–60 hrs, load and retry handling ~20–30 hrs, validation and QA ~20–30 hrs — and expect 2–4 weeks elapsed time.
What Is a Thena to Podium Migration?
A Thena to Podium migration is the process of extracting contacts, accounts, tickets, conversation history, tags, and metadata from Thena's B2B helpdesk platform and loading them into Podium as Contacts, Conversations, and Contact Attributes — while preserving as much relational context as the target platform permits.
This is a data-model transformation, not a platform swap. Thena is an AI-native customer support platform built for B2B teams. It unifies conversations from Slack, MS Teams, Discord, email, and live chat into a single ticketing workspace with account management, SLAs, routing, and AI-powered automation. (thena.ai) Podium is a customer interaction platform designed for local businesses, centered on SMS text messaging, webchat, Google and Facebook review management, text-based payments, and marketing campaigns.
The architectures diverge at the foundation. Thena operates on an account-based, multi-channel ticketing model where tickets are linked to Accounts and Contacts, with structured statuses, custom fields, SLAs, and workflow automations. Podium operates on a location-based, conversation-first messaging model where conversations are tied to physical business Locations and communication happens primarily over SMS and webchat.
Because these platforms serve different operational models, moving between them requires aggressive data transformation. There is no native migration path. Every record requires deliberate schema translation.
For related migration guides, see:
- Podium to Unthread Migration: Covers Podium-specific extraction and Unthread import patterns.
- Podium to HappyFox Migration: For teams moving from Podium to a ticketing-centric helpdesk.
- Thena to SurveySparrow Migration: Another Thena-origin migration with ticket mapping patterns.
Why Teams Migrate from Thena to Podium
This migration is a strategic channel shift, not an incremental upgrade. Teams move for specific operational reasons:
- Channel realignment. The business has shifted from B2B Slack-native support to SMS-first local customer engagement. Podium's core strength is text messaging and review management for brick-and-mortar and service businesses. This is the most common driver — a company that started as B2B SaaS pivots to (or acquires) a local-services model.
- Review and reputation management. Podium's automated review solicitation via Google and Facebook has no equivalent in Thena. Companies prioritizing online reputation move for this capability alone. Podium reports that businesses using its review automation see 2–3× increases in review volume.
- Payments consolidation. Podium handles text-to-pay invoicing natively. Teams that need support and payments in one platform see this as a simplification — one fewer vendor and one fewer integration.
- Cost model mismatch. Teams that over-provisioned Thena's B2B-grade features (AI agents, SLA engines, workflow builders) for a simpler use case find Podium's SMB-focused model cheaper to operate. However, cost comparison requires careful modeling: Thena uses seat-based pricing, while Podium uses custom quotes based on locations, SMS volume, and add-ons. "Cheaper" is only real after you model your actual usage pattern. (podium.com)
- Multi-location management. Podium's location hierarchy suits franchises and multi-site businesses that need per-location messaging, review management, and staff assignment.
Core Differences in Data Model and Architecture
| Dimension | Thena | Podium |
|---|---|---|
| Primary model | Account → Contact → Ticket | Organization → Location → Contact → Conversation |
| Core channels | Slack, MS Teams, Discord, email, live chat, web forms | SMS, webchat, Facebook Messenger, Google, Apple Business Chat |
| Ticket/Conversation model | Discrete tickets with lifecycle statuses (Open, Assigned, Resolved) | Continuous conversations tied to a contact and location |
| Account concept | First-class Account object with fields, notes, activities | No account object — contacts belong to Locations |
| Custom fields | Extensive custom ticket fields (text, dropdown, number, date, checkbox) | Contact Attributes (key-value strings). Podium's UI allows up to 8 custom fields per contact. (podium.com) |
| Workflows/Automation | Canvas-style workflow builder, AI agents, routing rules, SLAs | Data feed events, basic automation triggers |
| Knowledge base | Built-in help center with articles | None |
| Reviews | None | Core feature — Google, Facebook review management |
| Payments | None | Native text-to-pay invoicing |
| API architecture | REST API v2, x-api-key auth |
REST API v4, OAuth 2.0 |
| Identity | Slack IDs, email addresses, phone numbers | Phone numbers (primary for SMS), email |
| Threading | Multi-participant Slack threads with rich text, code blocks, inline files | Flat, chronological 1:1 SMS or webchat threads |
| Data residency | Not publicly documented; enterprise contracts may specify region | US-hosted infrastructure; no published EU data residency option |
The takeaway: Thena's B2B-grade abstractions (Accounts, SLAs, ticket fields, AI agents) largely do not exist in Podium. Migration requires deliberate decisions about what to preserve, what to flatten, and what to archive.
Migration Approaches
No vendor documents a native end-to-end Thena → Podium migration path. What you have is Thena CSV/API/webhooks on the source side and Podium CSV/API/webhooks on the destination side. (docs.thena.ai)
1. Native Export + CSV Import
Thena lets you export tickets from the current view as CSV, including core fields, customer data, SLA tracking, and custom fields; file upload fields are excluded. Typical Thena CSV columns include: ticketIdentifier, subject, status, priority, assignee, requester_name, requester_email, created_at, resolved_at, tags, and any custom field columns. (docs.thena.ai) Podium supports CSV upload for contacts and custom contact data.
- How it works: Export Thena views; split ticket columns from contact columns; normalize emails and phones; trim fields to Podium's contact schema; upload contacts; rebuild statuses and tags manually.
- When to use: Small datasets (under 5K contacts), contact-only migrations where conversation history is not required.
- Pros: Fast, cheap, no API credentials or code needed. Easy to dry-run.
- Cons: No conversation fidelity. Thena ticket exports do not include full conversation threading or message-level detail. Podium's CSV import handles contacts only — not conversations, messages, or tags. Custom fields require manual column mapping. No relationship preservation (Account → Contact links are lost). No incremental sync.
- Scalability: Small datasets only. Breaks down above 10K records due to manual transformation effort.
- Complexity: Low.
2. API-Based Migration (Thena APIs → Podium APIs)
This is the best default for a one-time, high-fidelity migration. Extract accounts, tickets, and comments from Thena's REST API v2; normalize contacts and account context; create/update Podium contacts; import customer-visible history with Podium's message import endpoint; and convert internal/private content into Podium conversation notes. (docs.thena.ai)
- How it works: Pull source records; build crosswalk tables; upsert contacts; import public comments as historical messages; store internal comments as notes; validate counts and samples.
- When to use: Any migration that requires conversation history, custom field mapping, or datasets above 5K records.
- Pros: Full control over data transformation logic. Repeatable and auditable. Can preserve conversation threads as imported messages. Handles custom fields, tags, and contact attributes.
- Cons: Requires engineering effort (80–160 hours for mid-size datasets). Must handle rate limits on both sides. Podium's message import endpoint has restrictions on threading. OAuth 2.0 token management for Podium adds complexity.
- Scalability: Handles enterprise-scale data with proper batching and rate-limit management.
- Complexity: High.
Thena API caveat: Thena's search docs list customer_contacts support as "coming soon," so contact data may need to be derived from accounts plus ticket requestors. Thena's comments endpoint is also documented as standard/enterprise tier only. Validate your available endpoints before starting. (docs.thena.ai)
3. Managed Migration Service
Engage a managed migration service that handles extraction, transformation, and loading. The service builds custom scripts tailored to your specific data model and handles edge cases.
- How it works: Source discovery; sample export; mapping workshop; staged load; QA and cutover.
- When to use: When engineering bandwidth is limited, the dataset is complex (custom fields, multi-level relationships), or the migration timeline is aggressive.
- Pros: No internal engineering effort. Proven patterns for handling edge cases. Typically includes validation and rollback planning.
- Cons: External cost. Requires sharing API credentials with the service provider. Vendor lock-in risk if you need re-migration later.
- Scalability: Enterprise-grade.
- Complexity: Low (for you).
4. Custom ETL Pipeline
A custom ETL is API migration with a real staging layer: raw extracts, normalized tables, crosswalk IDs, retry queues, reconciliation logs, and replayable loads.
- How it works: Extract to staging DB; transform with deterministic rules; batch loads into Podium; reconcile; rerun failed chunks. Use an orchestration tool (Airflow, Prefect, or custom Python scripts) with dedicated extract, transform, and load stages.
- When to use: Enterprise migrations with hundreds of thousands of records and complex data cleansing requirements, strict rollback expectations, or when the migration is part of a broader data infrastructure project.
- Pros: Maximum control and observability. Highly auditable and repeatable. Reusable for future migrations.
- Cons: Heaviest engineering investment. Requires infrastructure (scheduling, monitoring, error alerting). Overkill for one-time migrations under 50K records.
- Scalability: Unlimited, with proper infrastructure.
- Complexity: High.
5. Middleware/Integration Platforms (Zapier, Make)
Middleware is best treated as a delta-sync layer, not as the main migration engine. Thena can emit real-time platform events through webhooks (documented event types include ticket.created, ticket.updated, ticket.statusChanged, comment.added), and Podium supports both webhooks and API-driven contact/message workflows. Thena documents 256 KB event payload limits with truncation for oversized content, while Podium documents webhook queue expiry after about 10 days if a webhook stays disabled. (docs.thena.ai)
- How it works: Bulk load by CSV/API first; subscribe to source events; transform deltas; call Podium APIs; store idempotency keys.
- When to use: Ongoing sync of new records during a transition period. Not for historical bulk migration.
- Pros: Quick to stand up. Good for forwarding new tickets/contacts post-migration.
- Cons: Cannot migrate historical data in bulk. Rate limits and execution caps on free/standard plans. Limited transformation logic. No error recovery or rollback. Thena and Podium connector availability varies — may require custom webhook handling.
- Scalability: Small to medium ongoing volume.
- Complexity: Low to medium.
Migration Approach Comparison
| Approach | Historical Data | Conversations | Custom Fields | Scale | Eng. Effort | Complexity |
|---|---|---|---|---|---|---|
| CSV Export/Import | Partial | ❌ | Limited | Small | None | Low |
| API-Based ETL | ✅ | ✅ | ✅ | Enterprise | 80–160 hrs | High |
| Managed Service | ✅ | ✅ | ✅ | Enterprise | Minimal | Low (for you) |
| Custom ETL Pipeline | ✅ | ✅ | ✅ | Unlimited | 120–200 hrs | High |
| Middleware (Zapier/Make) | ❌ | Partial | Limited | Small | Low | Low |
Engineering Hour Breakdown (API-Based ETL, 10K–50K Tickets)
| Phase | Hours | Notes |
|---|---|---|
| Extraction scripts + pagination handling | 15–20 | Thena cursor pagination, auth validation, rate-limit backoff |
| Transform logic (schema mapping, field coercion) | 40–60 | Slack markdown stripping, E.164 normalization, account flattening, deduplication |
| Load scripts + retry handling | 20–30 | Podium OAuth refresh, rate limiting (especially 10 rpm message cap), idempotency |
| Validation and QA | 20–30 | Count reconciliation, field-level spot checks, UAT with end users |
| Edge case handling and debugging | 10–20 | Duplicates, missing phones, multi-participant threads, attachment decisions |
| Total | 80–160 | Lower end for clean data; upper end for complex custom fields and large attachment volumes |
Recommendations by Scenario
- Small business, < 5K contacts: CSV export/import for contacts, archive Thena conversations separately. The engineering cost to migrate Slack threads to SMS is rarely justified for low volumes.
- Mid-market, 5K–50K records: API-based migration or managed service.
- Enterprise, complex data, custom fields: Managed migration service or custom ETL pipeline.
- Ongoing sync (post-migration): Bulk backfill first, then middleware for new-record forwarding.
- Low engineering bandwidth: Managed migration service — unambiguously.
When to Use a Managed Migration Service
When NOT to Build In-House
- Your engineering team is shipping product features and cannot absorb 2–4 weeks of migration work
- The dataset includes custom ticket fields, multi-level relationships (Account → Contact → Ticket), or non-standard statuses that require careful transformation
- You need the migration completed in days, not weeks
- You lack experience with either Thena's or Podium's API quirks (OAuth token refresh, cursor pagination, rate-limit backoff)
Risks of DIY Migration
- Data loss from silent failures. Podium deduplicates contacts by phone number and returns HTTP 200 on contact creation even when deduplication silently merges records. Without field-level validation post-load, you will not know data was dropped. (docs.podium.com)
- Broken relationships. Thena's Account → Contact → Ticket hierarchy has no direct Podium equivalent. Flattening it incorrectly loses the ability to trace a customer's full history.
- Format corruption. Pushing Slack's
mrkdwnformat into Podium results in unreadable SMS histories. Slack messages contain user tags (<@U123456>), channel links (<#C123456|general>), and custom emojis (:custom_emoji:) that render as garbage in plain-text SMS. - Downtime during cutover. Without a delta-sync strategy, there is a window where new Thena tickets are created but not yet migrated, causing data gaps.
- OAuth token expiry mid-migration. Podium access tokens expire (typically after 2 hours). A multi-hour migration without automatic token refresh will silently fail partway through.
- Hidden engineering cost. What looks like a "weekend project" typically consumes 3–5× the estimated hours once edge cases (duplicate contacts, missing phone numbers, attachment handling) surface.
Pre-Migration Planning
Before writing migration code, audit your data and define the scope. Base the audit on actual source and target objects, not on wishful 1:1 mapping.
Data Audit Checklist
| Object | Key Questions |
|---|---|
| Accounts | How many? Do they have custom fields? Are there notes or activities attached? Parent/child account logic? |
| Contacts | How many? Are email/phone populated? What percentage lack phone numbers? |
| Tickets | Total count? Status distribution (open, resolved, archived)? Average messages per ticket? |
| Custom Fields | What types (text, dropdown, date, number, checkbox)? How many unique fields? Which are required? |
| Tags | How many unique tags? Are they used for routing or reporting? |
| Knowledge Base | How many articles? Are they referenced in ticket responses? |
| Workflows/SLAs | How many active workflows? Are any triggered by ticket fields? |
| Attachments | Average attachment size? Total storage? File types? |
GDPR and Data Residency Considerations
If your Thena instance handles EU personal data (common in B2B SaaS), evaluate:
- Podium's data residency: Podium's infrastructure is US-hosted. As of this writing, Podium does not publish EU data residency options. If you are subject to GDPR, confirm with Podium's sales team whether a Data Processing Agreement (DPA) covers your use case.
- Data in transit: The migration pipeline moves personal data (names, emails, phone numbers) through your ETL infrastructure. Ensure encryption in transit (TLS for all API calls) and at rest (encrypted staging database).
- Right to erasure: Once data is in Podium, you must be able to fulfill deletion requests. Podium's contact deletion API supports this, but verify it covers all associated messages and notes.
- Consent basis: If contacts were collected under a consent basis that specified Thena as the data processor, moving to Podium may require updated privacy notices.
Identify Data to Exclude
- Resolved tickets older than N months. Podium is a messaging platform, not an archive. Import only active or recently closed conversations.
- Knowledge base articles. Podium has no knowledge base. Export these separately (to Notion, Confluence, or a static site).
- Workflow configurations. Cannot be migrated. Rebuild in Podium's automation framework or as Data Feed rules.
- SLA policies. Podium has no SLA engine. Document them for manual recreation.
- AI agent configurations. Thena's AI agents, copilot, and MCP server configs are platform-specific. No migration path.
- Inactive Slack channels and dormant organizations. Archive before extraction to reduce noise.
Migration Strategy
- Big bang: Migrate all data in a single cutover window. Works for small datasets (< 10K records). Risk: if validation fails, rollback is manual. A weekend cutover is recommended to avoid channel confusion for customers.
- Phased: Migrate contacts first, then conversations, then tags/attributes. Allows validation at each stage. Recommended for mid-size datasets.
- Incremental/parallel: Migrate in batches by account or date range. Best for large datasets where you need to keep both systems running in parallel during transition. Pair a one-time backfill with webhook-based deltas rather than trying to push full history through middleware alone.
Podium Sandbox and Testing Environment
Podium does not offer a publicly documented sandbox environment. For pre-production testing, the recommended approach is:
- Create a dedicated test Location within your Podium organization
- Run all test migrations against this Location
- If validation fails, delete the test Location and recreate it
- Once validated, run the production migration against your real Location(s)
Confirm with your Podium account representative whether your plan tier supports multiple locations for testing purposes.
Data Model and Object Mapping
This is the hardest part of the migration. The two platforms share almost no structural alignment.
Object-Level Mapping
| Thena Object | Podium Equivalent | Transformation Notes |
|---|---|---|
| Account | Contact Tag + Contact Attribute | Podium has no Account object. Flatten to a tag (e.g., account:acme-corp) and a custom Contact Attribute (e.g., company_name: Acme Corp). Preserve thena_account_id as an attribute for traceability. |
| Contact | Contact | Direct map. Ensure phone number or email exists — Podium requires at least one. Phone numbers must be E.164 format. |
| Ticket | Conversation (imported messages) | Thena tickets become Podium conversation threads. Status metadata is simplified to open/closed. |
| Ticket Messages | Messages within a Conversation | Map as imported messages via Podium's message import endpoint. Strip Slack markdown. |
| Internal Notes / Private Comments | Conversation Notes | Internal-only markers attached to the Contact or Conversation. Prefix with source ticket ID. |
| Custom Ticket Fields | Contact Attributes | Ticket-level custom fields must be flattened to contact-level attributes. All non-string types must be coerced to strings. Max 8 custom fields in Podium. |
| Tags | Contact Tags | Direct map. Create tags in Podium first, then associate with contacts. |
| Knowledge Base Articles | ❌ No equivalent | Archive externally. |
| Workflows | ❌ No equivalent | Rebuild manually in Podium. |
| SLAs | ❌ No equivalent | Document and discard. |
| CSAT Responses | Feedback (Surveys) | Partial map — Podium's Feedback object tracks survey responses but schema differs. Podium Feedback stores rating (1–5), reviewBody, and source, which does not map cleanly to Thena's CSAT scale or channel-specific response metadata. |
| Attachments | Message Attachments | Podium's documented historical message import route does not accept attachment payloads. Re-upload via the live attachment send endpoint, or keep an archive with hosted links in notes. (docs.podium.com) |
No clean 1:1 destination exists in Podium for Thena Accounts, company hierarchies, or opportunity-style custom objects. Preserve source IDs in Podium attributes, tags, or note metadata, and keep a cross-reference table outside Podium. (docs.thena.ai)
Custom Field Type Coercion
Thena supports rich custom ticket fields. Podium Contact Attributes are simple key-value strings. Podium allows up to 8 custom fields per contact. (podium.com) All non-string types must be coerced:
- Dropdowns → string value of selected option
- Multi-select → comma-separated string (watch for Thena's semicolon-separated CSV export behavior — normalize delimiters before loading)
- Dates → ISO 8601 string (e.g.,
2025-03-15T00:00:00Z) - Numbers → string representation
- Checkboxes →
"true"/"false"string
Any custom metadata that exceeds Podium's 8-field limit must be serialized into a Contact Note or stored in an external system. Priority order for the 8 slots: choose fields that are actively used for filtering, routing, or reporting in day-to-day operations.
Field-Level Mapping
| Thena Field | Podium Field | Type | Notes |
|---|---|---|---|
contact.name |
contact.name |
String | Direct map |
contact.email |
contact.emails [].value |
String | Podium supports multiple emails |
contact.phone |
contact.phoneNumbers [].value |
String | Must be E.164 format (e.g., +14155552671) |
account.name |
contact.tags [] + contact.attributes [] |
String | Create tag account:acme-corp and attribute company_name |
account.primaryDomain |
contact.attributes [] |
String | Lowercase domain — useful for segmentation |
ticket.status |
Conversation status | Enum | Map OPEN → open, RESOLVED → closed |
ticket.created_at |
conversation.startedAt |
ISO 8601 | Direct map |
ticket.assignee_id |
conversation.assignedUser.uid |
UUID | Lookup via user mapping table |
ticket.tags [] |
contact.tags [] |
String [] | Create tags first, then associate |
ticket.custom_field (dropdown) |
contact.attributes [].value |
String | Selected option as string |
ticket.custom_field (date) |
contact.attributes [].value |
String | ISO 8601 string |
ticket.custom_field (checkbox) |
contact.attributes [].value |
String | "true" / "false" |
ticket.id / ticketIdentifier |
Note metadata or attribute | String | Preserve source IDs for audit and rollback |
message.text |
message.body |
String | Strip Slack markdown, resolve user tags |
message.created_at |
message.createdAt |
ISO 8601 | Convert UNIX to ISO 8601 if needed |
message.sender_type |
message.direction |
Enum | customer → inbound, agent → outbound |
Handling Relationships and Dependencies
Thena's hierarchy: Organization → Account → Contact → Ticket → Messages
Podium's hierarchy: Organization → Location → Contact → Conversation → Messages
The critical translations:
- Thena Accounts → Podium has no equivalent. Create a Contact Attribute called
company_nameand a Contact Tag for each account. This preserves grouping for filtering and reporting. - Thena Tickets → Each ticket becomes a Podium Conversation. Create the conversation, then import messages chronologically.
- Contact → Ticket relationships → In Podium, conversations are inherently linked to contacts. Ensure the contact is created before importing their conversations.
- User assignments → Map Thena agent IDs to Podium User UIDs. Use the Podium Users API to list available users and build the mapping table.
- Multi-participant threads → Podium uses a 1:1 conversation model. If a Thena ticket had multiple customer stakeholders replying, map all inbound messages to a single primary Contact and prepend the sender's name to the message body (e.g.,
[From: John Doe] The server is down.).
Migration Architecture
The architecture follows a standard Extract-Transform-Load (ETL) pattern, with a heavy emphasis on the Transform step.
Data Flow
Thena API (Extract) → Transform Layer → Podium API (Load)
│ │ │
├─ GET /v2/accounts ├─ Schema mapping ├─ POST /v4/contacts
├─ GET /v2/contacts ├─ Field coercion ├─ POST /v4/messages/import
├─ GET /v2/tickets ├─ Slack markdown ├─ POST /v4/contact_tags
├─ GET /v2/tags │ stripping ├─ PUT /v4/conversations
└─ GET /v2/attachments ├─ E.164 normalization └─ POST /v4/contact_attributes
├─ Deduplication
├─ Validation
└─ Error logging
Thena API Extraction
- Authentication: API key passed via
x-api-keyheader. Keys are generated from Dashboard → Organization Settings → Security and Access. Note: some Thena endpoint docs show bearer-token examples — validate the auth scheme in your tenant before automating. (docs.thena.ai) - Base URL:
https://bolt.thena.ai/rest/v2/ - Pagination: Cursor-based. Responses include pagination metadata for iterating through large datasets. Streaming supports up to 250 results per page.
- Rate limits: Standard Thena rate limits default to 60 requests per minute. Implement defensive throttling with exponential backoff. (docs.thena.ai)
- Key endpoints:
/workspace/tickets,/workspace/contacts,/workspace/accounts,/workspace/tags - Caveat: If Thena does not expose contacts directly through search, derive contacts from accounts plus ticket requestors/customer fields. (docs.thena.ai)
If Thena is heavily Slack-integrated, you may also need to cross-reference Slack's API for raw message payloads, user phone numbers, and threaded attachment downloads.
Podium API Loading
- Authentication: OAuth 2.0 with bearer tokens. Requires a developer account with client ID and client secret. Access tokens expire (typically after 2 hours) and must be refreshed using the refresh token grant.
- Base URL:
https://api.podium.com/v4/ - Rate limits: 300 requests per minute on most routes. Message sending is capped at 10 requests per minute. Attachment sends use a separate endpoint capped at 10 requests per minute with a 30 MB file size limit. HTTP 429 responses include a
Retry-Afterheader. - Pagination: Cursor-based with limits up to 100 per page. Cursors can expire — if they do, restart the request without a cursor.
- Key endpoints: Contacts (CRUD), Conversations, Messages (import + send), Contact Tags, Contact Attributes.
- API versioning: Podium recommends pinning an API version header (e.g.,
podium-version: 2021.4.1).
Podium's Location Requirement: Every contact creation, message send, and conversation update requires a valid locationUid. If your Podium account has multiple locations, you must decide which location each migrated contact belongs to before starting the load phase. There is no "global" contact pool.
Step-by-Step Migration Process
Step 1: Extract Data from Thena
Pull all relevant objects from Thena's API:
import requests
import json
import time
THENA_API_KEY = "your-thena-api-key"
BASE_URL = "https://bolt.thena.ai/rest/v2"
def get_thena_data(endpoint, params=None):
headers = {"x-api-key": THENA_API_KEY}
all_results = []
url = f"{BASE_URL}/{endpoint}"
while url:
response = requests.get(url, headers=headers, params=params)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
continue
response.raise_for_status()
data = response.json()
all_results.extend(data.get("results", []))
# Handle cursor-based pagination
url = data.get("next_cursor_url")
params = None # Params included in cursor URL
time.sleep(1.0) # Defensive: stay well under 60 rpm
return all_results
# Extract all core objects
accounts = get_thena_data("workspace/accounts")
contacts = get_thena_data("workspace/contacts")
tickets = get_thena_data("workspace/tickets")
tags = get_thena_data("workspace/tags")
print(f"Extracted: {len(accounts)} accounts, {len(contacts)} contacts, {len(tickets)} tickets")Step 2: Transform Data
Map Thena objects to Podium's schema. This step requires two critical utility functions that are the most common source of DIY migration failures.
Phone Number Normalization (E.164)
import re
def normalize_e164(phone: str, default_country: str = "1") -> str:
"""
Normalize a phone number to E.164 format.
Handles common formats: (415) 555-2671, 415-555-2671, 4155552671, +14155552671
"""
if not phone:
return ""
# Strip all non-digit characters except leading +
digits = re.sub(r'[^\d+]', '', phone)
# Remove leading + for uniform processing
if digits.startswith('+'):
digits = digits[1:]
# If it's already 11 digits starting with country code, format it
if len(digits) == 11 and digits.startswith(default_country):
return f"+{digits}"
# If it's 10 digits (US), prepend country code
if len(digits) == 10:
return f"+{default_country}{digits}"
# If it's already a valid international number (10-15 digits), prepend +
if 10 <= len(digits) <= 15:
return f"+{digits}"
# Return empty string for invalid numbers — flag for manual review
return ""Slack Markdown Stripping
import re
def strip_slack_markdown(text: str, user_lookup: dict = None) -> str:
"""
Convert Slack mrkdwn to plain text suitable for SMS/Podium messages.
Handles:
- User mentions: <@U123456> → @username or @unknown_user
- Channel links: <#C123456|general> → #general
- URLs: <https://example.com|Example> → Example (https://example.com)
- Bold: *text* → text
- Italic: _text_ → text
- Strikethrough: ~text~ → text
- Code blocks: ```code``` → code
- Inline code: `code` → code
- Custom emoji: :custom_emoji: → [custom_emoji]
- Block quotes: > text → text
"""
if not text:
return ""
# Resolve user mentions
def resolve_user(match):
user_id = match.group(1)
if user_lookup and user_id in user_lookup:
return f"@{user_lookup[user_id]}"
return f"@{user_id}"
text = re.sub(r'<@(U[A-Z0-9]+)>', resolve_user, text)
# Resolve channel links: <#C123456|general> → #general
text = re.sub(r'<#C[A-Z0-9]+\|([^>]+)>', r'#\1', text)
# Resolve URLs: <https://example.com|Example> → Example (https://example.com)
text = re.sub(r'<(https?://[^|>]+)\|([^>]+)>', r'\2 (\1)', text)
# Resolve bare URLs: <https://example.com> → https://example.com
text = re.sub(r'<(https?://[^>]+)>', r'\1', text)
# Remove code blocks (preserve content)
text = re.sub(r'```[\s\S]*?```', lambda m: m.group(0)[3:-3].strip(), text)
# Remove inline code markers
text = re.sub(r'`([^`]+)`', r'\1', text)
# Remove bold/italic/strikethrough markers
text = re.sub(r'\*([^*]+)\*', r'\1', text) # bold
text = re.sub(r'_([^_]+)_', r'\1', text) # italic
text = re.sub(r'~([^~]+)~', r'\1', text) # strikethrough
# Convert custom emoji to bracketed text
text = re.sub(r':([a-z0-9_+-]+):', r'[\1]', text)
# Remove block quote markers
text = re.sub(r'^>\s?', '', text, flags=re.MULTILINE)
return text.strip()Contact and Ticket Transformation
def transform_contact(thena_contact, thena_account=None):
"""Transform a Thena contact into Podium contact format."""
podium_contact = {
"name": thena_contact.get("name", ""),
"phoneNumbers": [],
"emails": [],
"tags": [],
"attributes": []
}
# Map phone (Podium requires E.164 format)
if thena_contact.get("phone"):
e164 = normalize_e164(thena_contact["phone"])
if e164:
podium_contact["phoneNumbers"].append({
"value": e164,
"type": "mobile"
})
else:
podium_contact["_warnings"] = [f"Invalid phone: {thena_contact['phone']}"]
# Map email
if thena_contact.get("email"):
podium_contact["emails"].append({
"value": thena_contact["email"]
})
# Flatten account to tag and attribute
if thena_account:
podium_contact["tags"].append(f"account:{thena_account['name']}")
podium_contact["attributes"].append({
"key": "company_name",
"value": thena_account["name"]
})
# Preserve source ID for traceability
podium_contact["attributes"].append({
"key": "thena_contact_id",
"value": str(thena_contact.get("id", ""))
})
# Map custom fields to attributes (respect 8-field limit)
custom_fields = thena_contact.get("custom_fields", [])
remaining_slots = 8 - len(podium_contact["attributes"])
for field in custom_fields[:remaining_slots]:
podium_contact["attributes"].append({
"key": sanitize_key(field["name"]),
"value": str(field["value"])
})
if len(custom_fields) > remaining_slots:
podium_contact["_overflow_fields"] = custom_fields[remaining_slots:]
return podium_contact
def sanitize_key(key: str) -> str:
"""Sanitize a custom field name for use as a Podium attribute key."""
return re.sub(r'[^a-z0-9_]', '_', key.lower().strip())[:50]
def transform_ticket_to_messages(thena_ticket, user_lookup=None):
"""Transform a Thena ticket into Podium message import format."""
public_messages = []
internal_notes = []
for msg in thena_ticket.get("messages", []):
visibility = msg.get("commentVisibility", "public")
cleaned_text = strip_slack_markdown(msg.get("text", ""), user_lookup)
if visibility in ("public", "customer"):
public_messages.append({
"body": cleaned_text,
"createdAt": msg.get("created_at"),
"direction": "inbound" if msg.get("sender_type") == "customer" else "outbound"
})
else:
internal_notes.append({
"body": f"[Thena #{thena_ticket.get('ticketIdentifier', '')}] {cleaned_text}",
"createdAt": msg.get("created_at")
})
return public_messages, internal_notesStep 3: Load into Podium
Load contacts first, then tags, then conversations and messages. Respect rate limits and handle OAuth token refresh throughout.
import time
from datetime import datetime, timedelta
class PodiumClient:
"""Podium API client with automatic token refresh and rate limiting."""
def __init__(self, client_id, client_secret, refresh_token, location_uid):
self.client_id = client_id
self.client_secret = client_secret
self.refresh_token = refresh_token
self.location_uid = location_uid
self.access_token = None
self.token_expiry = datetime.min
self.base_url = "https://api.podium.com/v4"
def _ensure_token(self):
"""Refresh OAuth token if expired or about to expire (5-min buffer)."""
if datetime.utcnow() < self.token_expiry - timedelta(minutes=5):
return
response = requests.post(
f"{self.base_url}/oauth/token",
json={
"grant_type": "refresh_token",
"client_id": self.client_id,
"client_secret": self.client_secret,
"refresh_token": self.refresh_token
}
)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
self.refresh_token = data.get("refresh_token", self.refresh_token)
# Podium tokens typically expire in 7200 seconds (2 hours)
self.token_expiry = datetime.utcnow() + timedelta(seconds=data.get("expires_in", 7200))
def _headers(self):
self._ensure_token()
return {
"Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json",
"podium-version": "2021.4.1"
}
def _request_with_retry(self, method, url, json_data=None, max_retries=5):
"""Execute request with rate-limit retry and token refresh."""
for attempt in range(max_retries):
self._ensure_token()
response = requests.request(method, url, json=json_data, headers=self._headers())
if response.status_code in (200, 201):
return response.json()
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(retry_after)
elif response.status_code == 401:
# Force token refresh
self.token_expiry = datetime.min
self._ensure_token()
else:
response.raise_for_status()
raise Exception(f"Max retries exceeded for {url}")
def create_contact(self, contact_data):
contact_data["locationUid"] = self.location_uid
return self._request_with_retry("POST", f"{self.base_url}/contacts", contact_data)
def import_message(self, message_data):
return self._request_with_retry("POST", f"{self.base_url}/messages/import", message_data)
# Usage
client = PodiumClient(
client_id="your-client-id",
client_secret="your-client-secret",
refresh_token="your-refresh-token",
location_uid="your-location-uid"
)
# Load contacts with rate limiting
contact_mapping = {} # thena_contact_id -> podium_contact_uid
errors = []
for contact in transformed_contacts:
try:
result = client.create_contact(contact)
contact_mapping[contact["attributes"][0]["value"]] = result["uid"] # thena_contact_id
except Exception as e:
errors.append({"contact": contact, "error": str(e)})
time.sleep(0.2) # 300 rpm = 5 rps
print(f"Created {len(contact_mapping)} contacts, {len(errors)} errors")Step 4: Rebuild Relationships and Import Conversations
- Create all Contact Tags in Podium first (POST to contact tag endpoints)
- Associate tags with contacts
- Import conversation messages chronologically per contact — public comments as imported messages, private comments as conversation notes
- Assign conversations to Podium users based on the agent mapping table
- For message imports, respect the 10 requests/minute cap — this is the bottleneck for large migrations
Store source IDs and account labels as attributes and keep the authoritative relationship table externally for reconciliation.
Step 5: Validate
Run count comparisons and field-level spot checks after each phase. See the Validation section below.
Edge Cases and Challenges
Missing Phone Numbers
Podium's core functionality is SMS-based. Thena contacts often only have email addresses. Contacts without phone numbers can be created in Podium but will not be reachable via SMS — only email or webchat. Before migration, run a query to determine what percentage of your contacts lack phone numbers. If it exceeds 30%, evaluate whether Podium is the right target platform for your customer base.
Duplicate Records
Podium deduplicates contacts by phone number. If two Thena contacts share the same phone, Podium will merge them silently — returning HTTP 200 without indicating that a merge occurred. Pre-migration action: Deduplicate in the transform layer before loading. Flag contacts with shared phone numbers for manual review. Clean up duplicate users in Thena before moving them to Podium — do not migrate bad data.
Multi-Participant Threads
If a Thena ticket had three different customer stakeholders replying in a Slack channel, Podium's 1:1 conversation model will not accommodate that natively. Map all inbound messages to a single primary Contact in Podium, prepending the actual sender's name to the message body (e.g., [From: John Doe] The server is down.). For tickets with more than 5 distinct participants, consider creating separate Podium contacts for the top 2–3 and linking them via a shared tag.
Ticket Moves Between Teams
When a Thena ticket is moved between teams, Thena creates a new destination ticket and archives the original. Treat these as lifecycle events, not accidental duplicates. Use the ticketIdentifier to trace the chain. (docs.thena.ai)
Attachments
Thena (via Slack) hosts files internally. Podium's documented historical message import route does not accept attachment payloads. (docs.podium.com) Options:
- Download files from Slack/Thena, host them on S3/GCS with signed URLs, and link them in Podium notes
- Re-upload only operationally critical files via Podium's live attachment send endpoint (separate rate limits: 10 rpm, 30 MB max per file)
- Keep a complete attachment archive externally with a crosswalk table linking
thena_ticket_id→ attachment URL
API Authentication Edge Cases
Thena's API introduction documents x-api-key auth, but some endpoint pages show bearer-token examples. Validate the auth scheme in your tenant before automating the extractor. (docs.thena.ai)
Podium's OAuth 2.0 tokens expire (typically after 2 hours) and must be refreshed. Build token refresh logic into your loader — see the PodiumClient class above. A stale token during a multi-hour migration will cause all subsequent API calls to fail with HTTP 401.
Custom Objects and Lead/Opportunity Data
If Thena uses lead/opportunity/deal custom objects, do not fake a relational object graph in Podium. Either flatten only the operationally useful state into Contact Status/tags and keep the full object in an external CRM, or keep Podium as the communication layer and push qualified lead context downstream through CRM sync/writeback patterns. (docs.thena.ai)
Limitations and Constraints
What Podium Cannot Replicate from Thena
| Thena Capability | Podium Status |
|---|---|
| Account-level CRM (notes, activities, health tracking) | ❌ Not available |
| Custom ticket field types (dropdown, multi-select, date, checkbox) | ⚠️ Flattened to string attributes (max 8 custom fields) |
| SLA policies and enforcement | ❌ Not available |
| Workflow automation builder | ⚠️ Limited — Data Feed events only |
| AI agents and copilot | ❌ Not available |
| Knowledge base / help center | ❌ Not available |
| CSAT collection in Slack/Teams | ⚠️ Podium has Feedback Surveys but different mechanism |
| Multi-channel (Slack, MS Teams, Discord) | ❌ Podium is SMS/webchat-focused |
| Ticket status lifecycle (custom statuses) | ⚠️ Conversations have open/closed only |
| Rich text (Slack markdown, code blocks, inline images) | ⚠️ Stripped to plain text for SMS |
| B2B Account hierarchy | ❌ Must use tags/attributes as workaround |
API Rate Limits Summary
| Platform | Endpoint | Limit |
|---|---|---|
| Thena | All endpoints | 60 requests/minute |
| Podium | Most endpoints | 300 requests/minute |
| Podium | Message sends | 10 requests/minute |
| Podium | Attachment sends | 10 requests/minute, 30 MB max |
At 10 messages/minute, importing 50K messages takes 83+ hours of continuous API calls. Plan accordingly — this is often the migration bottleneck.
Potential Data Loss Scenarios
- Conversation threading context: Thena's Slack-native threading (with internal triage threads, emoji reactions, related tickets) has no Podium equivalent. Internal threads are lost.
- Account-level context: Notes, activities, and account health metrics attached to Thena Accounts have no target in Podium.
- Workflow state: Active workflow executions, SLA countdowns, and routing rules are not transferable.
- Schema compression: Podium's 8-field custom field limit means any Thena dataset with more than 8 custom fields requires hard choices about what to preserve at the contact level vs. what gets serialized into notes.
- Rich text fidelity: Code blocks, tables, and inline images in Slack messages become plain text.
SMS Opt-in Compliance
Podium enforces strict SMS opt-in rules (TCPA compliance). Migrating a contact from Thena does not automatically grant you legal permission to text them. Before sending any SMS to migrated contacts:
- Verify you have documented opt-in consent for SMS communication
- If consent was only for email/Slack communication, you need fresh SMS opt-in
- Consider importing contacts as "do not message" and running a re-opt-in campaign
- Violations carry penalties of $500–$1,500 per unsolicited text under TCPA
Validation and Testing
Record Count Comparison
Compare transformed counts, not raw counts:
| Object | Thena Count | Podium Count | Delta | Action |
|---|---|---|---|---|
| Unique Requestors → Contacts | X | Y | X - Y | Investigate missing records |
| Public Comments → Imported Messages | X | Y | X - Y | Check for contacts without phone |
| Private Comments → Notes | X | Y | X - Y | Verify note creation |
| Tags | X | Y | X - Y | Verify tag creation |
| Attributes | X | Y | X - Y | Check type coercion failures |
Field-Level Validation
Sample 5–10% of migrated records (minimum 50 records). For each:
- Verify name, email, phone match between source and target
- Confirm custom fields (now attributes) have correct values
- Check that account tags are properly associated
- Verify conversation message count matches ticket message count
- Confirm phone numbers are properly E.164 formatted
- Check that conversation timestamps align
- Verify Slack markdown was properly stripped (no
<@U123>artifacts in messages)
Crosswalk Table Schema
Maintain this table throughout migration — it is your audit trail and rollback reference:
CREATE TABLE migration_crosswalk (
source_type VARCHAR(50), -- 'contact', 'ticket', 'message', 'tag'
source_id VARCHAR(255), -- Thena ID
dest_type VARCHAR(50), -- 'contact', 'conversation', 'message', 'tag'
dest_uid VARCHAR(255), -- Podium UID
checksum VARCHAR(64), -- SHA-256 of source record for drift detection
status VARCHAR(20), -- 'pending', 'created', 'failed', 'verified'
attempt_count INT DEFAULT 0,
error_message TEXT,
created_at TIMESTAMP DEFAULT NOW(),
verified_at TIMESTAMP
);UAT Process
- Migrate a test batch (100–500 records) to a Podium test Location
- Have end users verify 20–30 records manually
- Log into Podium and read the migrated conversations — ensure flattened threads are actually readable by human agents
- Test Podium's messaging functionality with migrated contacts
- Confirm review invites, campaigns, and payment flows work with migrated data
- Sign off before full migration
Rollback Planning
Podium does not have a bulk delete API for contacts. Rollback options:
- Delete contacts individually via API (rate-limited at 300 rpm — deleting 10K contacts takes ~33 minutes)
- Create the migration into a dedicated test Location, delete the Location if validation fails
- Maintain a complete Thena backup and re-migration script
For a deeper QA runbook, adapt our Post-Migration QA Checklist.
Post-Migration Tasks
Rebuild in Podium
- Automations: Thena workflows do not transfer. Rebuild review request automations, message templates, and data feed triggers in Podium. (podium.com)
- Team assignments: Recreate team/agent assignments for conversations based on your Podium user structure.
- Templates: Migrate frequently used Thena response snippets to Podium Message Templates.
- Web widgets: Replace Thena/Slack integration points on your website with Podium's Webchat widget.
User Training
Podium's workflow is fundamentally different from Thena's. Key training areas:
- Location-based contact management vs. account-based
- SMS-first communication patterns vs. Slack-native
- Review management and payment collection flows
- Podium's conversation interface vs. Thena's ticket board
- Understanding that historical imported messages are read-only and may lack the threading context agents are used to
Monitor for Data Inconsistencies
For 2–4 weeks post-migration:
- Spot-check 10 records daily for field accuracy
- Monitor Podium for duplicate contact creation (query the crosswalk table for contacts with shared phone numbers)
- Verify that imported conversation history renders correctly
- Track any contacts that fail to receive SMS (bad phone format)
- Watch for misassigned locations, missing tags, and missing note/message history
Best Practices
- Back up everything before migration. Export all Thena data via API and store locally as JSON. You cannot re-extract data after your Thena account is deactivated.
- Freeze Thena during final cutover. Put Thena into a read-only state during the final cutover window to prevent data drift.
- Run test migrations first. Migrate 100–500 records to a test Podium Location. Validate before committing to full migration.
- Validate incrementally. Check counts and sample records after each batch — not just at the end.
- Handle the Account gap explicitly. Decide upfront how Thena Accounts map to Podium constructs. Document the decision and communicate it to your team.
- Sanitize data before migration. Deduplicate contacts, normalize phone numbers to E.164, and resolve missing-email/missing-phone gaps in Thena first.
- Archive what does not transfer. Knowledge base articles, workflow configs, SLA policies, and AI agent settings should be exported and stored separately — not discarded.
- Plan for the phone number gap. Quantify the percentage of Thena contacts without phone numbers before migration. Decide whether to import them (limited Podium utility) or exclude them.
- Keep a crosswalk table from day one. Log at minimum:
source_type,source_id,dest_type,dest_uid,checksum,attempt_count, andstatus. - Account for the message import bottleneck. At 10 messages/minute, calculate your total message import time upfront. For 50K messages, this is 83+ hours — plan your cutover window accordingly.
For migration planning frameworks, see our Help Desk Data Migration Playbook.
Making the Right Call
A Thena-to-Podium migration is not a like-for-like swap — it is a platform transformation. You are moving from a B2B, Slack-native, account-based helpdesk to a local-business, SMS-native, location-based messaging platform. The data models share almost no structural overlap.
The technical migration is achievable, but the real work is in the schema translation decisions: how Accounts become Tags, how ticket fields flatten to string attributes, what data you are willing to archive rather than migrate, and how you handle the 8-custom-field ceiling in Podium. Make these decisions before writing any code.
The safe rule: use CSV only for contact seeding, use API ETL for historical conversations, and keep any Thena-only account or custom-object structure in an external archive or CRM.
If you have the engineering bandwidth and a clean dataset, an API-based ETL pipeline gets the job done. If you are working with complex custom fields, multi-account hierarchies, or a tight timeline, a managed migration service eliminates the risk of silent data loss and broken relationships.
Frequently Asked Questions
- Can I migrate Thena tickets directly into Podium?
- There is no native migration path between Thena and Podium. You need a custom API-based ETL pipeline to extract tickets from Thena's v2 API and load them as Conversations and imported Messages into Podium's v4 API. CSV export covers contacts but not full conversation history.
- What Thena data cannot be migrated to Podium?
- Podium has no equivalent for Thena's knowledge base articles, SLA policies, workflow automations, AI agent configurations, or Account-level CRM features (notes, activities, health tracking). These must be archived externally or rebuilt manually.
- How do Thena Accounts map to Podium?
- Podium has no Account object. Flatten Thena Accounts into Podium Contact Tags (e.g., 'account:acme-corp') and Contact Attributes (e.g., 'company_name: Acme Corp') to preserve grouping and filtering capability.
- What are Podium's API rate limits for data migration?
- Podium enforces 300 requests per minute on most API routes, with the message sending endpoint capped at 10 requests per minute. HTTP 429 responses include a Retry-After header. Build exponential backoff with jitter into your migration scripts.
- What is the biggest technical risk in a Thena to Podium migration?
- Silent data compression. Account context, custom objects, internal comments, and attachments do not have clean 1:1 destinations in Podium's simpler contact/conversation model. Podium also limits contacts to 8 custom fields, so heavily customized Thena datasets require hard choices about what to preserve.



