Send URLs via SMS through Twilio-Rails - ruby-on-rails

Working on a Rails project that needs to send a link to a record to someone via SMS.
/services/twilio_client.rb:
def send_text(job, message)
client = Twilio::REST::Client.new account_sid, auth_token
client.messages.create(
to: job.cell_number,
from: phone_number,
body: message
)
end
From Controller:
if job.save?
message = "#{#job.company} worker, you've got a new job. See it here:"
TwilioClient.new.send_text(#job, message)
In an ideal world, I could send them a link directly to the job via SMS, but Twilio won't accept ruby code as a media_url and dropping #{#job} in the message results in receiving the object #<Job:0x00007f0b60818338> in the SMS.
Clearly, this is a syntax issue, but try as I might I can't find a solution in the docs, the twilio-ruby gem, or examples published on the interweb.

I would change the interface of the send_message a bit:
# in /services/twilio_client.rb:
def send_text(number, message)
client = Twilio::REST::Client.new(account_sid, auth_token)
client.messages.create(to: number, from: phone_number, body: message)
end
And then call it from the controller like this:
if job.save?
message = "#{#job.company} worker, you've got a new job. See it here: #{media_url}"
TwilioClient.new.send_text(#job.cell_number, message)
# ...
The important fact here is that URL builders are only available in controllers and views in Rails per default. When you need a URL in another object like a service model then the easiest way is to generate it on the controller level and pass it to the service.

Related

The application goes offline when trying to send more than thousands of emails in Rails with AWS SES

I have implemented a platform using rails, and the goal is to send thousands of emails to customers with one click. The concept is that an email array runs each loop and inside each loop runs send email functionality like below.
#emails = ['abc#gmai.com', 'abc#example.com'] # More than 3 thousands
#emails.each do |email|
aws_email_sender(email, #email_subject, #email_body_html)
end
And the email function is like below:
def aws_email_sender(recipient, subject, htmlbody)
sender = "hello#example.com"
awsregion = "ap-west-1"
# The HTML body of the email.
htmlbodycontent = "#{htmlbody}"
# The email body for recipients with non-HTML email clients.
textbody = "This email was sent with Amazon SES using the AWS SDK for Ruby."
# Specify the text encoding scheme.
encoding = "UTF-8"
# Create a new SES resource and specify a region
ses = Aws::SES::Client.new(region: awsregion)
# Try to send the email.
begin
# Provide the contents of the email.
resp = ses.send_email({
destination: {
to_addresses: [recipient]
},
message: {
body: {
html: {
charset: encoding,
data: htmlbodycontent
},
text: {
charset: encoding,
data: textbody,
},
},
subject: {
charset: encoding,
data: subject,
},
},
source: sender,
});
# If something goes wrong, display an error message.
rescue Aws::SES::Errors::ServiceError => error
puts "Email not sent. Error message: #{error}"
end
end
The email is sending well by AWS but my rails application has gone down like
A timeout occurred, error code 524
I couldn't get the breaking point, why has my application gone down every time?
Thanks in Advance
If 524 is an HTTP status code then it means...
Cloudflare was able to make a TCP connection to the website behind them, but it did not reply with an HTTP response before the connection timed out.
Meaning your Rails app is behind a Cloudflare proxy. Cloudflare received an HTTP request, forwarded it to your app, waited around for your app to respond, but your app never did. A more detailed explanation can be found here.
Probably because it's trying to send emails to 3000 people one-by-one.
There's two strategies to fix this.
Use Bulk Email
Since the content of the email is the same for everyone, use an email template to send bulk email using the #send_bulk_templated_email method.
You can send to up to 50 addresses at a time, so use #each_slice to loop through emails in slices of 50.
This will be more efficient, but your app will still be waiting around for 3000/50 = 60 AWS API calls. At worst it will still time out. At best the user will be waiting around for a form submission.
Use A Background Job
Anytime your app needs to do something that might take a lot of time, like using a service or a large database query, consider putting it into a background job. The Rails app queues up a job to send the emails, and then it can respond to the web request while the mailing is handled in the background. This has other advantages: errors calling the service won't cause an error for the user, and failed jobs due to a temporary service outage can automatically be retried.
In Rails this is done with ActiveJob and you could write a job class to send your mail.
Use ActionMailer
However, Rails also offers a class specifically for sending email in the background: ActionMailer. You can have ActionMailer use AWS with the aws-sdk-rails gem.
config.action_mailer.delivery_method = :ses

Twilio Conference Call

I was able to implement basic Voice Conferencing but I feel my implementation may be lacking.
client = Client('ACxxxxxxxx', '34xxxxxxxxx')
#app.route('/', methods=["GET", "POST"])
def home():
form = ConferenceList() #form made using flask-wtf
if form.validate_on_submit():
contact_1 = form.data['contact_1'] #callee1
contact_2 = form.data['contact_2'] #callee2
from_ = form.data['from_'] #caller
response = VoiceResponse()
with Dial() as dial:
if from_ == MODERATOR:
dial.conference(
'Conf',
start_conference_on_enter=True,
end_conference_on_exit=True
)
else:
dial.conference('Conf', start_conference_on_enter=False)
response.append(dial)
'''here I feel could be a bottleneck'''
add_user(contact_1, conference_name='Conf', label='laed#1')
add_user(contact_2, conference_name='Conf', label="consumer")
return Response(str(response), 'text/xml')
return render_template('hello.html', form=form)
def add_user(contact, conference_name, label):
participant = client.conferences(conference_name).\
participants.create(
label=label, #label for participant
beep='onEnter',
record=True,
from_='from_', #same as above
to=str(contact)
)
if __name__ == '__main__':
app.run(debug=True, port=8000)
Basically the submit button triggers the / endpoint and the conference starts.
I feel there could be an issue with this implementation as I plan on cleaning it up and pushing to production (salesperson can make a conference call to leads on the app). Is there something I could have done better?
The voice(one client-one client) utilizes the Twilio Voice SDK, it there a way I could tweak it for conferencing?
You only need to respond with TwiML to a webhook from Twilio. In this case it appears that you are responding with TwiML to your application's front end when a submit button is pressed.
So, you can drop all the TwiML:
#app.route('/', methods=["GET", "POST"])
def home():
form = ConferenceList() #form made using flask-wtf
if form.validate_on_submit():
contact_1 = form.data['contact_1'] #callee1
contact_2 = form.data['contact_2'] #callee2
'''here I feel could be a bottleneck'''
add_user(contact_1, conference_name='Conf', label='laed#1')
add_user(contact_2, conference_name='Conf', label="consumer")
return "whatever"
Since the settings you are trying to apply in the TwiML do not apply to the participants joining the conference, you need to adjust your add_user function to use them. In this case, the startConferenceOnEnter and endConferenceOnExit parameters for the moderator should be sent when you create the participant. It might be easier to write two methods, add_user and add_moderator, to make things clear:
def add_user(contact, conference_name, label):
participant = client.conferences(conference_name).\
participants.create(
label=label, #label for participant
beep='onEnter',
record=True,
from_='from_', #same as above
to=str(contact),
start_conference_on_enter=False
)
def add_moderator(contact, conference_name, label):
participant = client.conferences(conference_name).\
participants.create(
label=label,
beep='onEnter',
record=True,
from_='from_',
to=str(contact),
start_conference_on_enter=True,
end_conference_on_exit=True
)
Then call different functions for the different participants:
add_moderator(contact_1, conference_name='Conf', label='laed#1')
add_user(contact_2, conference_name='Conf', label="consumer")
When you call add_user or add_moderator it will make an API request and slow down your server response. If you wanted to offload those requests to a worker, that would make your response quicker. But for 2 API requests, it is likely not a problem.
One other thing you might want to consider is the consumer experience. If they answer the phone before your agent does, then they will be greeted with hold music. You might want to architect it so that the application calls the agent first and only once they have picked up it then dials the consumer. Just worth considering.
Edit
After further explanation, you are now telling me that you want to make the call from the browser using the Twilio Voice SDK for JS.
To make outbound calls with the JS SDK you need to create an access token which includes an outgoing application sid, which refers to a TwiML application. That TwiML application has a voice URL. When you place the call with the SDK, Twilio makes a webhook request to the voice URL of your TwiML app. Your application can perform actions and return TwiML to tell Twilio what to do with the call.
When you create the call with the JS SDK you can pass parameters to the call.
const device = new Device(token);
const call = await device.connect({
params: {
To: ["+15551234567", "+145557654321"]
}
});
Those parameters are sent with the webhook request to your TwiML App voice URL. You can then use a response very similar to your original code to respond here, because you need to return TwiML to the request from Twilio, and start calls to the other participants in the call.
#app.route('/conference', methods=["POST"])
def conference():
const numbers = request.form["To"]
response = VoiceResponse()
with Dial() as dial:
dial.conference(
'Conf',
start_conference_on_enter=True,
end_conference_on_exit=True
)
response.append(dial)
for number in numbers:
add_user(number, conference_name='Conf')
return Response(str(response), 'text/xml')
This code receives the To parameter, a list of numbers to dial into this conference, builds the TwiML response that will put the browser caller into a conference call, places outbound calls to the numbers to dial them into the conference and then returns the TwiML to Twilio. The dialler in the browser will start the conference and the other participants will arrive in the conference when they answer the phone.
In this case you don't make the request to your server yourself, you use the JS SDK to trigger the call and let Twilio make the request to your server. As mentioned in the comments, you may want to offload the API calls to create participants to a background job so that you can respond to the webhook request quicker, but that is beyond the scope of this answer.

Twilio Ruby - Unable to create record: The 'From' number +15005550006 is not a valid phone number, shortcode, or alphanumeric sender ID

Hello i'm trying to send SMS with Twilio and Ruby.
Everything worked fine until today when i'm trying to send sms with the error :
Unable to create record: The 'From' number +15005550006 is not a valid phone number, shortcode, or alphanumeric sender ID.
Here is my code, i don't understand why it is not working. For informations, i'm using my test credentials.
def boot_twilio
#twilio_number = ENV['TWILIO_NUMBER']
account_sid = ENV['TWILIO_ACCOUNT_SID']
auth_token = ENV['TWILIO_AUTH_TOKEN']
#client = Twilio::REST::Client.new(account_sid, auth_token)
end
def notification(user)
boot_twilio
#client.messages.create(
from: #twilio_number,
to: user.phone
body: "test"
)
end
Thank you in advance !
Please check again your credentials, and maybe your bills too at twilio console.
In order to send a test sms using the Twilio test numbers, you need to make sure you are using your test credentials. From the looks of this code it seems you may be using your actual credentials.
from: https://github.com/twilio/twilio-node/issues/125

How to receive data in ActionCable Channel without JS?

I'm writing a Rails application that uses WebSockets to communicate with other machines (no browser and client side logic in this process). I have a channel:
class MachinesChannel < ApplicationCable::Channel
def subscribed
...
end
def unsubscribed
...
end
def handle_messages
...
end
end
To receive the data the only way I know about is the JavaScript client:
ActionCable.createConsumer('/cable').subscriptions.create 'MachinesChannel',
received: (message) ->
#perform('handle_messages')
I can call server side methods from JS via #perform() method.
Is there any way to omit the JS part and somehow directly handle the incoming data in MachinesChannel?
The ideal situation would be to have the handle_messages method accept a data argument and have this metod called on incoming data.
After looking into ActionCable source code I got the following solution. You just have to create a method in MachinesChannel that you want to be called, e.g. handle_messages(data). Then, in the client that connects to your websocket, you need to send a message in the following format (example in ruby):
id = { channel: 'MachinesChannel' }
ws = WebSocket::Client::Simple.connect(url)
ws.send(JSON.generate(command: 'message', identifier: JSON.generate(id), data: JSON.generate(action: 'handle_messages', foo: 'bar', biz: 'baz')))
action has to be the name of the method you want to be called in MachinesChannel. The rest of key-values are whatever you want. This the date you can receive in the ActionCable channel.
Recently a gem action_cable_client has been release which seems exactly perfect for this kind of usage. I haven't used it, so I don't know how it really works.
Instead of:
def handle_messages
...
end
This works for me:
def receive(data)
puts data
...
end

Using Google's Audit API to monitor google apps email

I need to get some admin users using google apps gmail the ability to monitor their employees email. Have you used Google's Audit API to do this.
I wish there there was a way for the admins to just click a view my users email but that doesn't be the case.
If it matters the application is a rails app. The email is completely done on googles mail through google apps. Anyone that has done this any advice would be helpful.
Update! 500 points for this one!
I'm using ruby on rails hosting an app on heroku. The email is completely hosted with google apps standard, not business so we will have to upgrade, and the DNS is with zerigo which you already know if you use heroku.
Well, I hadn't planned on extending the gdata-ruby-util gem :), but here's some code that could be used for the Google Audit API based on Google's documentation. I only wrote a create_monitor_on method, but the rest are pretty easy to get.
Let me know if it works or needs any rewrites and I'll update it here:
class Audit < GData::Client::Base
attr_accessor :store_at
def initialize(options = {})
options[:clientlogin_service] ||= 'apps'
options[:authsub_scope] ||= 'https://apps-apis.google.com/a/feeds/compliance/audit/'
super(options)
end
def create_monitor_on(email_address)
user_name, domain_name = email_address.split('#')
entry = <<-EOF
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:apps='http://schemas.google.com/apps/2006'>
<apps:property name='destUserName' value='#{#store_at}'/>
<apps:property name='beginDate' value=''/>
<apps:property name='endDate' value='2019-06-30 23:20'/>
<apps:property name='incomingEmailMonitorLevel' value='FULL_MESSAGE'/>
<apps:property name='outgoingEmailMonitorLevel' value='FULL_MESSAGE'/>
<apps:property name='draftMonitorLevel' value='FULL_MESSAGE'/>
<apps:property name='chatMonitorLevel' value='FULL_MESSAGE'/>
</atom:entry>
EOF
return true if post('https://apps-apis.google.com/a/feeds/compliance/audit/mail/monitor/'+domain_name+'/'+user_name, entry).status_code == 201
false
end
end
Then use it elsewhere like this:
auditor = Audit.new
auditor.store_at = 'this-username'
auditor.clientlogin(username, password)
render :success if auditor.create_monitor_on('email-address#my-domain.com')
My suggestion is to create one core email address that all the email monitors are sent to, so your admins' inboxes aren't slammed with everyone else's mail. Then in your Rails app, use Net::IMAP to download the messages you want from that master email account. i.e., you can create a link that says "View Joe's Email" and the method does something like this:
require 'net/imap'
imap = Net::IMAP.new('imap.gmail.com', 993, true)
imap.login('this-username#my-domain.com', password)
imap.select('INBOX')
messages = []
imap.search(["TO", "joe#email.com").each do |msg_id|
msg = imap.fetch(msg_id, "(UID RFC822.SIZE ENVELOPE BODY[TEXT])")[0]
body = msg.attr["BODY[TEXT]"]
env = imap.fetch(msg_id, "ENVELOPE")[0].attr["ENVELOPE"]
messages << {:subject => env.subject, :from => env.from[0].name, :body => body }
end
imap.logout
imap.disconnect
Then you can put those messages in your view -- or send them all in one bulk email, or whatever you want to do.

Resources