Add attachment to email with SendGrid using rails - ruby-on-rails

I've created a hello_world method which sends an email with Sendgrid. I am trying to include an attachment. I've found the following line in another stackoverflow answer: mail.attachments['test.txt'] = File.read("#{Rails.root}/public/test.txt")
This however generates the following error:
Completed 500 Internal Server Error in 17ms (ActiveRecord: 3.4ms)
TypeError - no implicit conversion of String into Integer:
app/controllers/messages_controller.rb:32:in `hello_world'
app/controllers/messages_controller.rb:65:in `create'
Mailing code in controller:
def hello_world(company, message)
from = Email.new(email: "test+#{current_user.auth_token}#example.com")
to = Email.new(email: 'hello#pim.gg')
subject = 'TEST from dev'
content = Content.new(type: 'text/plain', value: "#{company.email} #{current_user} #{current_user.first_name} #{current_user.last_name} #{message.text}")
mail = SendGrid::Mail.new(from, subject, to, content)
mail.attachments['test.txt'] = File.read("#{Rails.root}/public/test.txt")
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
response = sg.client.mail._('send').post(request_body: mail.to_json)
puts response.status_code
puts response.body
puts response.headers
end

According to documentation of sendgrid-ruby gem adding-attachments should be like this:
attachment = SendGrid::Attachment.new
attachment.content = Base64.strict_encode64(File.open(fpath, 'rb').read)
attachment.type = 'application/vnd.openxmlformatsofficedocument.spreadsheetml.sheet'
attachment.filename = fname
attachment.disposition = 'attachment'
attachment.content_id = 'Reports Sheet'
mail.add_attachment(attachment)

Related

Refactoring an API request on Rails

I have a rails worker using redis/sidekiq where I send some data to an API (Active Campaign), so I normally use all the http configurations to send data. I want to have it nice and clean, so it's part of a refactor thing. My worker currently looks like this:
class UpdateLeadIdWorker
include Sidekiq::Worker
BASE_URL = Rails.application.credentials.dig(:active_campaign, :url)
private_constant :BASE_URL
API_KEY = Rails.application.credentials.dig(:active_campaign, :key)
private_constant :API_KEY
def perform(ac_id, current_user_id)
lead = Lead.where(user_id: current_user_id).last
url = URI("#{BASE_URL}/api/3/contacts/#{ac_id}") #<--- need this endpoint
https = bindable_lead_client.assign(url)
pr = post_request.assign(url)
case lead.quote_type
when 'renter'
data = { contact: { fieldValues: [{ field: '5', value: lead.lead_id }] } }
when 'home'
data = { contact: { fieldValues: [{ field: '4', value: lead.lead_id }] } }
when 'auto'
data = { contact: { fieldValues: [{ field: '3', value: lead.lead_id }] } }
else
raise 'Invalid quote type'
end
pr.body = JSON.dump(data)
response = JSON.parse(https.request(pr).read_body).symbolize_keys
if response.code == '200'
Rails.logger.info "Successfully updated contact #{ac_id} with lead id #{lead.lead_id}"
else
raise "Error creating contact: #{response.body}"
end
end
def bindable_lead_client
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http
end
def post_request
post_request_ = Net::HTTP::Put.new(url)
post_request_['Accept'] = 'application/json'
post_request_['Content-Type'] = 'application/json'
post_request_['api-token'] = API_KEY
post_request_
end
end
But whenever I run this I get:
2022-07-28T00:52:08.683Z pid=24178 tid=1s1u WARN: NameError: undefined local variable or method `url' for #<UpdateLeadIdWorker:0x00007fc713442be0 #jid="e2b9ddb6d5f4b8aecffa4d8b">
Did you mean? URI
I don't want everything stuck in one method. How could I achieve to make this cleaner?
Thanks.
Pure ruby wise, The reason you get the error is because your method definition bindable_lead_client is missing the url argument. Hence undefined variable.
So def should look something like:
def bindable_lead_client (url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http
end
and call:
bindable_lead_client(url)
As for how to make this code better, falls under question being too subjective under StackOverflow guidelines, which encourage you to ask more specific questions.

Ruby on Rails use of methods within methods

So I am writing a simple controller that will receive parameters from a Postrequest to my API. And I want to keep things cleaner and nice, so I wrote something like this:
def create
update_contact
end
def update_contact
create_token
url = URI("https://acme.api-us1.com/api/3/contacts/#{active_campaign_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request['api-token'] = API_KEY
data = { contact: { fieldValues: [{ field: '1', value: contact[:email_token] }] } }
request.body = JSON.dump(data)
response = http.request(request)
end
def create_token
active_campaign_id = params[:contact][:id].to_i
generate_token = SecureRandom.urlsafe_base64(12)
contact = Contact.find_or_initialize_by(active_campaign_id: active_campaign_id, email_token: generate_token)
contact.save!
end
But whenever I run this turns into:
*** NameError Exception: undefined local variable or method `active_campaign_id'
and same goes for email_token
*** NameError Exception: undefined local variable or method `email_token'
Now whenever I do this:
def create
update_contact
end
def update_contact
active_campaign_id = params[:contact][:id].to_i
generate_token = SecureRandom.urlsafe_base64(12)
contact = Contact.find_or_initialize_by(active_campaign_id: active_campaign_id, email_token: generate_token)
contact.save!
url = URI("https://acme.api-us1.com/api/3/contacts/#{active_campaign_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request['api-token'] = API_KEY
data = { contact: { fieldValues: [{ field: '1', value: contact[:email_token] }] } }
request.body = JSON.dump(data)
response = http.request(request)
end
It works! Why is that? How can I structure my code or methods as clean as possible?
And what resources could make me understand more accessing methods in rails?
Thanks for the help!
If you want to keep methods you have and make it works you can achieve this by doing next refactoring:
def create
update_contact
end
def update_contact
contact = create_contact
url = URI("https://acme.api-us1.com/api/3/contacts/#{contact.active_campaign_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
request['api-token'] = API_KEY
data = { contact: { fieldValues: [{ field: '1', value: contact.email_token }] } }
request.body = JSON.dump(data)
response = http.request(request)
end
def create_contact
Contact.create_with(
email_token: SecureRandom.urlsafe_base64(12)
).find_or_create_by!(
active_campaign_id: params.dig(:contact, :id)&.to_i
)
end
And probably you need to use create_with method because every time when you will try to find Contact by fields pair email_token and active_campaign_id SecureRandom.urlsafe_base64(12) will generate a new email token and you always will have new object created instead of getting it from database.
It looks like you are trying to access the 'active_campaign_id' and 'email_token' variables in the 'update_contact' method, but those variables are only defined in the 'create_token' method. Try moving the 'update_contact' method inside the 'create_token' method so that it has access to those variables.
More info you could find here:
https://guides.rubyonrails.org/v6.1/action_view_overview.html#using-action-view-with-rails
https://guides.rubyonrails.org/v6.1/engines.html#using-a-controller-provided-by-the-application
https://guides.rubyonrails.org/v6.1/security.html#user-management
https://guides.rubyonrails.org/v6.1/2_3_release_notes.html#action-controller
https://guides.rubyonrails.org/v6.1/active_record_multiple_databases.html#automatic-swapping-for-horizontal-sharding

Display variables as plain text in rails

In my rails app I save some data as below.
def send_share_company(address, cid, from, council_id)
co = Council.find_by(id: council_id)
c = Company.find_by(id: cid)
#a = address.split(',')
#f = from
n = Notification.find_by(id: 'ad4e3718-0689-413b-8a52-96887e0d2aba')
message =eval(n["body"])
from = Email.new(email: 'noreply#my.network', name: "my Network")
subject = + cl["title"] + ' - Event of the Week: ' + c["name"]
to = Email.new(email: a.to_s)
content = Content.new(type: 'text/html', value: message)
mail = Mail.new(from, subject, to, content)
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
pp sg.client.mail._("send").post(request_body: mail.to_json) unless Rails.env.staging?
end
I used another form to add message to the database. Then I retrieve it using,
n = Notification.find_by(id: 'ad4e3718-0689-413b-8a52-96887e0d2aba')
My message as below. This is the data which I have save in database.
Hello,
{#f} thought you should take a look at the company, #{c["name"]}.
When I display this in my app it displays as above. But I want display plain text.Is there way to do this?

Ruby on Rails: How to attach a file to an email

I am trying to send an email with an attachment from my Rails project. I am using the Google API specifically the gmail_v1 API.
I have been able to get my code to send an email with a subject and a body, but have not been able to attach a CSV. The name of the CSV is "results.csv"
m = Mail.new(
to: "to#gmail.com",
from: "from#gmail.com",
subject: "Test Subject",
body:"Test Body")
m.attachments['shoes.csv'] = {mime_type: 'results.csv', content: CSV}
message_object = Google::Apis::GmailV1::Message.new(raw:m.to_s)
service.send_user_message("me", message_object)
Without the line:
m.attachments['shoes.csv'] = {mime_type: 'results.csv', content: CSV}
The code works, but without the attachment. What is the correct way to add the attachment?
You are sending wrong arguments to the attachments.
attachments should be send as below
attachments['shoes.csv'] = { mime_type: 'text/csv', content: File.read("path/to/csv/or/generator/methos") }
Updated code will be as
m = Mail.new(
to: "to#gmail.com",
from: "from#gmail.com",
subject: "Test Subject",
body:"Test Body")
m.attachments['shoes.csv'] = { mime_type: 'text/csv', content: File.read("path/to/csv/or/generator/methos") }
message_object = Google::Apis::GmailV1::Message.new(raw:m.to_s)
service.send_user_message("me", message_object)
Hope this will help

Sending HTML email using gmail API in ruby

I am creating a ruby script and it should do the above. Over the day I was trying to crack I way to send an HTML email to a selected number of emails addresses. There is no clear documentation on how I should do, So please I will appreciate you helping.
Here is my code, The script is successfully authorizing a user and picking the code to access his/her gmail account. Now I want to send the HTML email on behalf of that user.
require 'rubygems'
require 'google/api_client'
require 'launchy'
CLIENT_ID = 'my_app_Id_on_gmail_developers_console'
CLIENT_SECRET = 'the_secret_key'
OAUTH_SCOPE = 'https://mail.google.com/'
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Create a new API client & load the Google Drive API
client = Google::APIClient.new(:application_name => 'Ruby Gmail sample',
:application_version => '1.0.0')
gmail = client.discovered_api('gmail', "v1")
# Request authorization
client.authorization.client_id = CLIENT_ID
client.authorization.client_secret = CLIENT_SECRET
client.authorization.scope = OAUTH_SCOPE
client.authorization.redirect_uri = REDIRECT_URI
uri = client.authorization.authorization_uri
Launchy.open(uri)
# Exchange authorization code for access token
$stdout.write "Enter authorization code: "
client.authorization.code = gets.chomp
client.authorization.fetch_access_token!
#testing if it is working well by counting the emails.
#emails = client.execute(
api_method: gmail.users.messages.list,
parameters: {
userId: "me"},
headers: {'Content-Type' => 'application/json'}
)
count = #emails.data.messages.count
puts "you have #{count} emails "
# Pretty print the API result
jj #emails.data.messages
how can I do this? is there a way I can an external html file which is the email file to be sent. then I can sent this file using the script?
I partially accept the answer above since you can send an email through STMP pretty easily but with the gmail API it's even easier. According your code it should looks like this:
message = Mail.new
message.date = Time.now
message.subject = 'Supertramp'
message.body = "<p>Hi Alex, how's life?</p>"
message.content_type = 'text/html'
message.from = "Michal Macejko <michal#macejko.sk>"
message.to = 'supetramp#alex.com'
service = client.discovered_api('gmail', 'v1')
result = client.execute(
api_method: service.users.messages.to_h['gmail.users.messages.send'],
body_object: {
raw: Base64.urlsafe_encode64(message.to_s)
},
parameters: {
userId: 'michal#macejko.sk'
},
headers: { 'Content-Type' => 'application/json' }
)
response = JSON.parse(result.body)
For multi-part email with the attachment:
message = Mail.new
message.date = Time.now
message.subject = 'Supertramp'
message.from = "Michal Macejko <michal#macejko.sk>"
message.to = 'supetramp#alex.com'
message.part content_type: 'multipart/alternative' do |part|
part.html_part = Mail::Part.new(body: "<p>Hi Alex, how's life?</p>", content_type: 'text/html; charset=UTF-8')
part.text_part = Mail::Part.new(body: "Hi Alex, how's life?")
end
open('http://google.com/image.jpg') do |file|
message.attachments['image.jpg'] = file.read
end
Just my input. I was able to create a script that emailed html to multiple users in about 100 lines. Without using an api. You need to look into using smtp. It is very simple. You define a server for it to use and then you use it's "send_message" method. Here's a link to a good site! GOOD SITE
I can't post my whole code here for security reasons however this should get you started
class Email_Client
attr_accessor :message_contents, :subject
def initialize(sender_name, receiver_name, sender_email, receiver_email)
#sender_name = sender_name
#receiver_name = receiver_name
#sender_email = sender_email
#receiver_email = receiver_email
end
def send_html
message = <<MESSAGE
From: #{#sender_name} <#{#sender_email}>
To: #{#receiver_name} <#{#receiver_email}>
MIME-Version: 1.0
Content-type: text/html
Subject: #{subject}
#{message_contents}
MESSAGE
Net::SMTP.start('SeRvEr_HeRe') do |smtp|
smtp.send_message message,
#sender_email,
#receiver_email
end
end

Resources