Mike responsys

@dandemeyere @florrain @12and1studio 

3 parts...

1. `add_to_list`
 * in member.rb, there's an `add_to_list` method
 * takes in a list (InteractObject) and an optional subscription bool (defaults to false)
 * if member doesn't already exists, member will be added
 * if member already exists, nothing will be done 
 * to test...
```ruby
newMember = Responsys::Member.new('newTestEmail@thredup.com')
interactObject = Responsys::Api::Object::InteractObject.new("!MasterData","CONTACTS_LIST")
newMember.add_to_list(interactObject, true)
```
    * find the list on Responsys Interact and verify the email was added and the 
EMAIL_PERMISSION_STATUS_ is OPT IN


2. `Recipient` and `RecipientData`
 * some API calls require the `RecipientData` object, which is just a hash consisting of { recipient: `Recipient`, optionalData: `OptionalData[]` }
 * `Recipient`
   * the `Recipient` object is a hash consisting of { listName: `InteractObject` , recipientId: `long` , customerId: `string` , emailAddress: `string` , mobileNumber: `string`, emailFormat: `EmailFormat`}
    * all parts of `Recipient` hash are optional, HOWEVER each key must be present
    * `Recipient` is much like `InteractObject` in that a `Recipient` object is merely relevant information Responsys needs to find a row/member in a list, BUT isn't required to be a real member. You could create a `Recipient` with bogus information and nothing will error (eventually when an API call is made it will return "cannot find user", though) 
 * `OptionalData`
   * because this is optional, I haven't built out this API object yet. It currently defaults to a blank hash { name: "", value: "" }
 * to test...
    * newRecipient = Responsys::Api::Object::Recipient.new(emailAddress: 'michael.rocco@thredup.com')
    * OR newRecipient = Responsys::Api::Object::Recipient.new(recipientId: 123456, emailAddress: 'michael.rocco@thredup.com')
    * newRecipient.to_hash
    * newRecipientData = Responsys::Api::Object::RecipientData.new(newRecipient)
    * newRecipientData.to_hash

3. `trigger_message`
  * this method compiles necessary data for the `:trigger_campaign_message` API call
  * `trigger_message` takes in a campaign `InteractObject`, and desired recipients `RecipientData[]`
  * the API requires recipients to be in hash-form so `hash_recipients` is used
    * `hash_recipients` will morph an array of RecipientData (or a single instance of RecipientData) into a hash
  * after the API is called, the success statuses are returned into `outcome` (if invalid `Recipient` data was entered and no match could be found in the list, this is where a failure will be reported. Responsys won't send to anybody not in the default list for a campaign)
  * `outcome` (and the hashed `recipients`) are passed into a `check_failures` method which will print out the specific `Recipient` object(s) that could not be found in a given list
 * to test...
    * client = Responsys::Api::Client.instance
    * r = Responsys::Api::Object::Recipient.new(emailAddress: 'michael.rocco@thredup.com')
       * change the emailAddress to something you have access to AND exists in the campaign's default list (CONTACTS_LIST is the default list)
       * look at part 1 of this PR to add a new email to the list
       * EMAIL_PERMISSION_STATUS_ must be set to OPT IN
    * rd = Responsys::Api::Object::RecipientData.new(r)
    * r2 = Responsys::Api::Object::Recipient.new(emailAddress: "shouldfail@email.com")
       * this email should NOT exist in the campaign's default list (CONTACTS_LIST is the default list)
    * rd2 = Responsys::Api::Object::RecipientData.new(r2)
    * campaign = Responsys::Api::Object::InteractObject.new("~System","test")
    * client.trigger_message(campaign, [rd, rd2])
    * r2 should print out in the console as failed and r1 should receive an email that says THREDUPPPPP
        * this error handling can't stay as print statements lol
because i switched the instance variables to snake_case and Responsys requires camelCase, i'm going to not use the loop because it would get a little sloppy