Automatic emails using bash and sendgrid

Problem

You would like to be able to automatically send some emails (ie reports) from a linux server, using a bash script that runs in cronjob, without installing an email server on the linux server.

Solution

  • Create an account in Sendgrid and follow the directions for using WebAPI with curl
    here
  • Set up your bash script to use the API key and have a script like the following. If you want to use big or multiple files you will need to use a temporary file for the base64 encoding, as in the example below, as there is a limit in curl.
    #!/bin/bash
    
    # Email setup
    SENDGRID_API_KEY="your_sendgrid_api_key"
    FILENAME_ATTACH="title_of_your_attachment"
    FILENAME_ZIP="the_path_to_your_zip_file"
    FILENAME_BASE64_TMP="the_path_to_temporary_base64_encoding"
    EMAIL_TO="email_to_address"
    EMAIL_SUBJECT="email_subject"
    EMAIL_FROM="email_from_address"
    EMAIL_MESSAGE="your_email_message"
    
    function email_exports()
    {
      FILENAME_BASE64=$(base64 -w0 $FILENAME_ZIP);
    
      REQUEST_DATA='{"personalizations": [{
                            "to": [{ "email": "'"$EMAIL_TO"'" }],
                            "subject": "'"$EMAIL_SUBJECT"'"
                    }],
                    "from": {
                            "email": "'"$EMAIL_FROM"'"
                            },
                    "content": [{
                            "type": "text/plain",
                            "value": "'"$EMAIL_MESSAGE"'"
                            }],
                    "attachments": [{
                            "content": "'"$FILENAME_BASE64"'",
                            "filename": "'"$FILENAME_ATTACH"'"
                            }]
      }';
    
      # We need to store the base64 locally as the text
      # is too big for sending directly with curl
       echo $REQUEST_DATA > $FILENAME_BASE64_TMP
    
       curl -X "POST" "https://api.sendgrid.com/v3/mail/send" \
            -H "Authorization: Bearer $SENDGRID_API_KEY" \
            -H "Content-Type: application/json" \
            -d "@$FILENAME_BASE64_TMP";
    
       rm $FILENAME_BASE64_TMP
    }
    
    email_exports
  • Add your script to crontab
  • Changing default editor for crontab from nano to vim

    Problem

    You would like to change the default editor for crontab from nano to vim.

    Solution

    Add the following to the ~/.selected_editor:

    SELECTED_EDITOR="/usr/bin/vim"

    or run the select-editor and choose vim-basic (4)

    server# select-editor 
    
    Select an editor.  To change later, run 'select-editor'.
      1. /bin/ed
      2. /bin/nano        <---- easiest
      3. /usr/bin/mcedit
      4. /usr/bin/vim.basic
      5. /usr/bin/vim.tiny
    
    Choose 1-5 [2]: 4
    

    Running script/runner in production environment

    Problem
    Following from a previous post about email scheduling with runner and cron, it turns out that the runner default behaviour is to run in the development environment.

    Solution
    Although by reading the help for the script/runner, there is a suggestion to run it with the -e production added to the end, it doesn’t seem to be working.

    The solution to make it running in the production environment was to delete the first line (shebang) from step 3 on this post

    #!/usr/bin/env /path_to_your_app/script/runner

    and then use the following in the cron setup:

    RAILS_ENV=production /path/to/your_ror_project/script/runner /path/to/your_ror_project/lib/email_scheduler.rb

    Have a look on paragraph Alternative Usage here

    Ruby on Rails email scheduling using runner and cron

    Problem
    You want to send emails from a Ruby on Rails application, when there is a specific condition on a database table. If the database table gets modified by another application outside Rails you cannot use an observer model.

    Solution
    We already assume that:

    • You are using a database
    • You have a model named voicemail (id, number_id, audio, created_at, updated_at)
    • You have a model named number (id, voicemail_email_set, voicemail_email, ….)
    • A mail server to use (smtp in our case)
    • Another application (voice application) populates the voicemail table but with empty updated_at values

    So the steps we have to follow are:

    1. Change the settings in your config/environment.rb file to use the settings for your mail server, and make sure you restart your application after the changes:
      ActionMailer::Base.smtp_settings = {
        :address        => "yourmailserver.com",
        :port           =>  25,
        :domain         => "your.domain.com",
        :authentication => :login,
        :user_name      => "your_smtp_username",
        :password       => "your_smtp_password",
        :raise_delivery_errors  => true}
    2. Create your mailer model (ie voicemail_mailer.rb), in app/models:
      class VoicemailMailer < ActionMailer::Base
         # We need the open-uri to be able to open url *** if the file to attach is in an http location ***
        require 'open-uri'
       
        def sent(email_to,email_from,email_subject,email_body,voicemail_to_send)
          # Check to see if we have a file for the email body message
          @subject    = email_subject
          @body       = email_body
          @recipients = email_to
          @from       = email_from
          @sent_on    = Time.now
          
          # Split the file in directory and filename
          file_path = File.split(voicemail_to_send)
          file_dir  = file_path[0]
          file_name = file_path[1]
          
          # Get the file
          tmp_file = open(voicemail_to_send).read
          
          part( :content_type => "application/wav", 
                :disposition => "attachment; filename=#{file_name}", 
                :transfer_encoding => "base64") do |attachment|
                  attachment.body = tmp_file
          end
        end
      end
      
    3. Create your email scheduler in file lib/email_scheduler.rb:
      #!/usr/bin/env /path_to_your_app/script/runner
      
      # get all the voicemails that have not been sent yet 
      voicemails_to_email = VoiceMail.find(:all, :conditions => 'updated_at is null')
      
      # For all the voicemails we have, send them and update the field date_sent
      for vm2email in voicemails_to_email do
        # Get the number for the voicemail
        number = Number.find(vm2email.number_id)
        
        # check to see if the send to email is set for the number
        if number.voicemail_email_set
          # Get number details (email_to,email_from etc)
          email_to          = number.voicemail_email
          voicemail_to_send = vm2email.audio
          # Set other details
          email_from      = 'Service@yourdomain.com'
          email_subject   = 'Please find attached your voicemail message'
          email_body      = "Received on: #{Time.now} \n for number: #{number.phone_no}"
      
          # Now send the email
          VoicemailMailer.deliver_sent(email_to,email_from,email_subject,email_body,voicemail_to_send)
      
          # And update the record's date_sent field
          vm2email.updated_at = Time.now
          vm2email.save
        end
      end
      
    4. Create a task in your crontab that runs the scheduler (every five minutes):
      0,5,10,15,20,25,30,35,40,45,50,55 * * * * path_to_your_ror_app/lib/email_scheduler.rb