Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ VONAGE_SERVER_HOSTNAME=''

# RCS Brand Name, check formatting requirements. For instance, no spaces allowed
RCS_SENDER_ID=''

# WhatsApp Business Account Number (linked to your Vonage application)
VONAGE_WHATSAPP_NUMBER=''
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby-3.3.5
ruby-3.4.6
127 changes: 127 additions & 0 deletions WHATSAPP_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# WhatsApp Integration Setup Guide

This guide will help you set up and use the WhatsApp messaging functionality that has been added to your Rails application.

## What Was Added

The following components have been integrated into your existing Rails app:

### 1. Database
- **Migration**: `db/migrate/20251015112429_create_whatsapp_messages.rb`
- **Model**: `app/models/whatsapp_message.rb`
- Stores WhatsApp message records with fields: `to`, `from`, `text`, `status`, `message_uuid`, `is_inbound`

### 2. Controller
- **File**: `app/controllers/outbound_whatsapp_controller.rb`
- **Actions**:
- `new` - Display the form for sending WhatsApp messages
- `create` - Send a text WhatsApp message
- `interactive` - Send an interactive WhatsApp message with reply buttons

### 3. View
- **File**: `app/views/outbound_whatsapp/new.html.erb`
- Contains forms for both text and interactive WhatsApp messages

### 4. Routes
Added to `config/routes.rb`:
```ruby
get '/outbound_whatsapp/new', to: 'outbound_whatsapp#new', as: :new_outbound_whatsapp
post '/outbound_whatsapp', to: 'outbound_whatsapp#create', as: :outbound_whatsapp
post '/outbound_whatsapp/interactive', to: 'outbound_whatsapp#interactive', as: :interactive_whatsapp
```

### 5. Environment Configuration
Added `VONAGE_WHATSAPP_NUMBER` to `.env.example`

## Setup Instructions

### Step 1: Run the Migration
```bash
rails db:migrate
```

### Step 2: Configure Your Vonage WhatsApp Application

1. Log in to your [Vonage Dashboard](https://dashboard.nexmo.com/)
2. Create a new application (or use an existing one) and enable the **Messages** capability
3. Add webhook URLs for:
- **Inbound**: `https://your-domain.com/inbound` (or use ngrok for testing)
- **Status**: `https://your-domain.com/status`
4. Click **Generate public and private key**
5. Move the downloaded `private.key` file to the root of your Rails app
6. Note your **Application ID**
7. Click **Save**
8. Under the **Link external accounts** tab, link your WhatsApp number

### Step 3: Update Your .env File

Add the following to your `.env` file (create one if it doesn't exist):

```bash
VONAGE_APPLICATION_ID=your_application_id_here
VONAGE_PRIVATE_KEY=./private.key
VONAGE_WHATSAPP_NUMBER=14157386102 # Your WhatsApp Business number
```

### Step 4: Test the Integration

1. Start your Rails server:
```bash
rails s
```

2. Navigate to: `http://localhost:3000/outbound_whatsapp/new`

3. You should see two forms:
- **Send a WhatsApp Message** - For sending text messages
- **Try an Interactive Message** - For sending messages with reply buttons

## Features

### Text Messages
Send simple text messages to any WhatsApp number. The form includes:
- **From**: Your WhatsApp Business number (pre-filled from ENV)
- **To**: Recipient's WhatsApp number (format: +14155551234)
- **Message**: The text content to send

### Interactive Messages
Send messages with reply buttons that users can tap to respond. The example includes:
- A header ("Delivery time")
- Body text with a question
- Footer text with additional info
- Three reply buttons with different time slots

You can customize the interactive message structure in the `interactive` action of `OutboundWhatsappController`.

## How It Works

1. **User fills out the form** and submits
2. **Controller receives the data** and creates a `WhatsappMessage` record
3. **Vonage client is initialized** with your credentials
4. **Message is sent** via the Vonage Messages API
5. **Response is checked** - if successful (HTTP 202), the `message_uuid` is saved
6. **User is redirected** back to the form with a success notice

## Next Steps

As mentioned in the tutorial, this implementation covers sending messages. To build a complete WhatsApp integration, you'll want to add:

1. **Inbound message handling** - Receive messages from users
2. **Status webhooks** - Track message delivery and read status
3. **Interactive response handling** - Process which button the user clicked

These features would require additional controllers and webhook endpoints, similar to the existing `inbound_sms` and `sms_message_status` controllers in your app.

## Troubleshooting

- **Make sure your WhatsApp number is linked** to your Vonage application
- **Verify your .env file** has the correct credentials
- **Check that private.key** is in the root directory
- **Ensure the recipient number** is in E.164 format (e.g., +14155551234)
- **For testing**, you can use the [Vonage Sandbox for WhatsApp](https://developer.vonage.com/en/messages/concepts/messages-api-sandbox)

## Resources

- [Vonage Messages API Documentation](https://developer.vonage.com/en/messages/overview)
- [WhatsApp Business API Guide](https://developer.vonage.com/en/messages/concepts/whatsapp)
- [Vonage Ruby SDK](https://github.com/Vonage/vonage-ruby-sdk)
97 changes: 97 additions & 0 deletions app/controllers/outbound_whatsapp_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
class OutboundWhatsappController < ApplicationController
def new
@whatsapp_message = WhatsappMessage.new
end

def create
@whatsapp_message = WhatsappMessage.new(safe_params)

if @whatsapp_message.save
deliver(@whatsapp_message)
redirect_to :new_outbound_whatsapp, notice: 'WhatsApp message sent!'
else
flash[:alert] = 'Something went wrong'
render :new
end
end

def interactive
unless params[:to].present?
redirect_to :new_outbound_whatsapp, alert: "Recipient number is required!"
return
end

interactive_message = {
from: ENV["VONAGE_WHATSAPP_NUMBER"],
to: params[:to],
channel: "whatsapp",
message_type: "custom",
custom: {
type: "interactive",
interactive: {
type: "button",
header: {
type: "text",
text: "Delivery time"
},
body: {
text: "Which time would you like us to deliver your order at?"
},
footer: {
text: "Please allow 15 minutes either side of your chosen time."
},
action: {
buttons: [
{
type: "reply",
reply: { id: "slot-1", title: "15:00" }
},
{
type: "reply",
reply: { id: "slot-2", title: "16:30" }
},
{
type: "reply",
reply: { id: "slot-3", title: "17:15" }
}
]
}
}
}
}

response = vonage.messaging.send(**interactive_message)
Rails.logger.info("📤 Sent interactive message to #{params[:to]}: #{response.inspect}")

redirect_to :new_outbound_whatsapp, notice: "Interactive message sent to #{params[:to]}!"
end

private

def safe_params
params.require(:whatsapp_message).permit(:to, :from, :text)
end

def vonage
Vonage::Client.new(
application_id: ENV["VONAGE_APPLICATION_ID"],
private_key: ENV["VONAGE_PRIVATE_KEY"]
)
end

def deliver(whatsapp_message)
response = vonage.messaging.send(
message_type: "text",
text: whatsapp_message.text,
to: whatsapp_message.to,
from: whatsapp_message.from,
channel: "whatsapp"
)

if response.http_response.code == "202"
whatsapp_message.update(
message_uuid: response.entity.attributes[:message_uuid]
)
end
end
end
2 changes: 2 additions & 0 deletions app/models/whatsapp_message.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class WhatsappMessage < ApplicationRecord
end
39 changes: 39 additions & 0 deletions app/views/outbound_whatsapp/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<div class="whatsapp-form">
<h1>Send a WhatsApp Message</h1>

<%= form_for @whatsapp_message, url: outbound_whatsapp_path do |f| %>
<div class="field">
<%= f.label :from, "From (Your WhatsApp Number)" %>
<%= f.text_field :from, value: ENV['VONAGE_WHATSAPP_NUMBER'], placeholder: '+14157386102' %>
</div>

<div class="field">
<%= f.label :to, "To (Recipient WhatsApp Number)" %>
<%= f.text_field :to, placeholder: '+14155551234' %>
</div>

<div class="field">
<%= f.label :text, "Message" %>
<%= f.text_area :text, rows: 4, placeholder: "Type your message..." %>
</div>

<div class="actions">
<%= f.submit "Send Message" %>
</div>
<% end %>

<hr>
<h3>Try an Interactive Message</h3>
<p>Send a sample interactive WhatsApp message with reply buttons.</p>

<%= form_with url: interactive_whatsapp_path, method: :post, local: true do |f| %>
<div class="field">
<%= f.label :to, "To (Recipient WhatsApp Number)" %>
<%= f.text_field :to, placeholder: '+14155551234', required: true %>
</div>

<div class="actions">
<%= f.submit "Send Interactive Message" %>
</div>
<% end %>
</div>
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@

# For InboundCalls controller, create
resources :inbound_calls, only: [:create]

# For OutboundWhatsapp controller, new & create & interactive
get '/outbound_whatsapp/new', to: 'outbound_whatsapp#new', as: :new_outbound_whatsapp
post '/outbound_whatsapp', to: 'outbound_whatsapp#create', as: :outbound_whatsapp
post '/outbound_whatsapp/interactive', to: 'outbound_whatsapp#interactive', as: :interactive_whatsapp
end
14 changes: 14 additions & 0 deletions db/migrate/20251015112429_create_whatsapp_messages.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class CreateWhatsappMessages < ActiveRecord::Migration[8.0]
def change
create_table :whatsapp_messages do |t|
t.string :to
t.string :from
t.text :text
t.string :status
t.string :message_uuid
t.boolean :is_inbound

t.timestamps
end
end
end
13 changes: 12 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading