Files
chatwoot/app/services/twilio/template_sync_service.rb
Muhsin Keloth 99997a701a feat: Add twilio content templates (#12277)
Implements comprehensive Twilio WhatsApp content template support (Phase
1) enabling text, media, and quick reply templates with proper parameter
conversion, sync capabilities, and feature flag protection.

###  Features Implemented

  **Template Types Supported**

  - Basic Text Templates: Simple text with variables ({{1}}, {{2}})
  - Media Templates: Image/Video/Document templates with text variables
  - Quick Reply Templates: Interactive button templates
- Phase 2 (Future): List Picker, Call-to-Action, Catalog, Carousel,
Authentication templates

  **Template Synchronization**

- API Endpoint: POST
/api/v1/accounts/{account_id}/inboxes/{inbox_id}/sync_templates
  - Background Job: Channels::Twilio::TemplatesSyncJob
  - Storage: JSONB format in channel_twilio_sms.content_templates
  - Auto-categorization: UTILITY, MARKETING, AUTHENTICATION categories

 ###  Template Examples Tested


  #### Text template
```
  { "name": "greet", "language": "en" }
```
  #### Template with variables
```
  { "name": "order_status", "parameters": [{"type": "body", "parameters": [{"text": "John"}]}] }
```

  #### Media template with image
```
  { "name": "product_showcase", "parameters": [
    {"type": "header", "parameters": [{"image": {"link": "image.jpg"}}]},
    {"type": "body", "parameters": [{"text": "iPhone"}, {"text": "$999"}]}
  ]}
```
#### Preview

<img width="1362" height="1058" alt="CleanShot 2025-08-26 at 10 01
51@2x"
src="https://github.com/user-attachments/assets/cb280cea-08c3-44ca-8025-58a96cb3a451"
/>

<img width="1308" height="1246" alt="CleanShot 2025-08-26 at 10 02
02@2x"
src="https://github.com/user-attachments/assets/9ea8537a-61e9-40f5-844f-eaad337e1ddd"
/>

#### User guide

https://www.chatwoot.com/hc/user-guide/articles/1756195741-twilio-content-templates

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2025-08-29 16:13:25 +05:30

117 lines
2.9 KiB
Ruby

class Twilio::TemplateSyncService
pattr_initialize [:channel!]
def call
fetch_templates_from_twilio
update_channel_templates
mark_templates_updated
rescue Twilio::REST::TwilioError => e
Rails.logger.error("Twilio template sync failed: #{e.message}")
false
end
private
def fetch_templates_from_twilio
@templates = client.content.v1.contents.list(limit: 1000)
end
def update_channel_templates
formatted_templates = @templates.map { |template| format_template(template) }
channel.update!(
content_templates: { templates: formatted_templates },
content_templates_last_updated: Time.current
)
end
def format_template(template)
{
content_sid: template.sid,
friendly_name: template.friendly_name,
language: template.language,
status: derive_status(template),
template_type: derive_template_type(template),
media_type: derive_media_type(template),
variables: template.variables || {},
category: derive_category(template),
body: extract_body_content(template),
types: template.types,
created_at: template.date_created,
updated_at: template.date_updated
}
end
def mark_templates_updated
channel.update!(content_templates_last_updated: Time.current)
end
def client
@client ||= channel.send(:client)
end
def derive_status(_template)
# For now, assume all fetched templates are approved
# In the future, this could check approval status from Twilio
'approved'
end
def derive_template_type(template)
template_types = template.types.keys
if template_types.include?('twilio/media')
'media'
elsif template_types.include?('twilio/quick-reply')
'quick_reply'
elsif template_types.include?('twilio/catalog')
'catalog'
else
'text'
end
end
def derive_media_type(template)
return nil unless derive_template_type(template) == 'media'
media_content = template.types['twilio/media']
return nil unless media_content
if media_content['image']
'image'
elsif media_content['video']
'video'
elsif media_content['document']
'document'
end
end
def derive_category(template)
# Map template friendly names or other attributes to categories
# For now, use utility as default
case template.friendly_name
when /marketing|promo|offer|sale/i
'marketing'
when /auth|otp|verify|code/i
'authentication'
else
'utility'
end
end
def extract_body_content(template)
template_types = template.types
if template_types['twilio/text']
template_types['twilio/text']['body']
elsif template_types['twilio/media']
template_types['twilio/media']['body']
elsif template_types['twilio/quick-reply']
template_types['twilio/quick-reply']['body']
elsif template_types['twilio/catalog']
template_types['twilio/catalog']['body']
else
''
end
end
end