Orange is my favorite color

This is going to be mostly code because I don’t have time to really annotate much. However, if you’re an Adobe ColdFusion developer and you also use (or are considering) the very good Batchbook Social CRM then you may also want to use their REST API in order to programmatically create or read your contact data.

While Batchbook does have the pretty cool web forms which can capture contact data from any ole web form, it doesn’t give you total flexibility with filling in customized data fields. In our case, we wanted to create companies rather than individuals as part of a sales pipeline so we needed to have more control than the web forms currently allow.

Start with a contact form

Here’s the HTML form that we’re using – it still uses the original web form field names so all I’ve done here is changed the form ACTION to point at my CFM instead of Batchbook:

<form method="post" action="https://ourserver.com/form.cfm">
<input type="hidden" name="location[address][country]" value="US" />
<h2>Sign-up Now!</h2>
<table class="form">
<tr><td><label>Company *</label></td></tr>
<tr><td><input type="text" name="company[name]" size="30" /></td></tr>
<tr><td><label>Account Type *</label></td></tr>
<tr><td><select name="supertags[sales first contact][plan]"><option value="">Select a plan - you can change later</option><option value="Plan 1">Plan Uno</option><option value="Plan 2">Plan Dos</option><option value="Plan 3">Plan Tres</option></select></td></tr>
<tr><td><label>First Name *</label></td></tr>
<tr><td><input type="text" name="contact_details[first_name]" size="30" /></td></tr>
<tr><td><label>Last Name *</label></td></tr>
<tr><td><input type="text" name="contact_details[last_name]" size="30" /></td></tr>
<tr><td><label>Email *</label></td></tr>
<tr><td><input type="text" name="location[email]" size="30" /></td></tr>
<tr><td><label>Phone *</label></td></tr>
<tr><td><input type="text" name="location[phone]" size="30" /></td></tr>
<tr><td><label>Organization Address</label></td></tr>
<tr><td><input type="text" name="location[address][address_1]" size="30" /></td></tr>
<tr><td><label>Address 2</label></td></tr>
<tr><td><input type="text" name="location[address][address_2]" size="30" /></td></tr>
<tr><td><label>City, State Zip</label></td></tr>
<tr><td><input type="text" name="location[address][city]" size="15" />, <input type="text" name="location[address][state]" size="4" /> <input type="text" name="location[address][zip_code]" size="10" /></td></tr>
<tr><td><label>Company URL</label></td></tr>
<tr><td><input type="text" name="location[website]" size="40" /></td></tr>
<tr><td><label>Do you have an existing service?  If so, which:</label></td></tr>
<tr><td><input type="text" name="supertags[sales first contact][existing_service]" size="40" /></td></tr>
<tr><td><label>Date of next event:</label></td></tr>
<tr><td><input type="text" name="supertags[sales first contact][date_of_first_event]" size="15" /><br /><small>Format date like mm/dd/yyyy</small></td></tr>
<tr><td><label>Questions/Comments</label></td></tr>
<tr><td><textarea name="supertags[sales first contact][customer_comments]" rows="10" cols="50"></textarea></td></tr>
<tr class="submit"><td><input class="button" type="submit" value="Request Account" /></td></tr>
</table>
</form>

It’s the same contact form you’ve whipped up 100 times before. If you’re using the built in web forms, the field names must match what is above. If I was writing my form handler from scratch I would have selected more normalized names.

Note that we a hidden field at the beginning – not all of the data must be user editable. Of course, I could also just set it in my form handler. The country value is a holdover from the original web form.

Process those fields

Our next step is to process that form submission with ColdFusion and use the Batchbook API to create my contacts and populate my custom data fields:

< !--- credentials --->
<cfset variables.api_key = 'YOUR-SECURITY-KEY' />< !--- get this from "Your Account", right column --->
<cfset variables.root_uri = 'https://[YOUR HOST].batchbook.com/service' />

<cfhttp url="#variables.root_uri#/companies.xml" method="post" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no">
	<cfhttpparam name="company[name]" value="#form['company[name]']#" type="formfield" />
	<cfhttpparam name="company[notes]" value="" type="formfield" />
</cfhttp>
< !--- cfdump var="#cfhttp#" --->

<cfif structKeyExists(cfhttp, "responseheader") AND isStruct(cfhttp.responseheader) AND structKeyExists(cfhttp.responseheader, "location")>

	<cfhttp url="#cfhttp.responseheader.location#" method="get" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no">
	</cfhttp>
	< !--- cfdump var="#cfhttp#" --->

	<cfset xmlCompany = xmlParse(cfhttp.fileContent) />
	<cfset id = xmlCompany.company.id.xmlText />

	< !--- now add location to company --->
	<cfhttp url="#variables.root_uri#/companies/#id#/locations.xml" method="post" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no">
		<cfhttpparam name="location[label]" value="work" type="formfield" />
		<cfhttpparam name="location[email]" value="#form['location[email]']#" type="formfield" />
		<cfhttpparam name="location[website]" value="#form['location[website]']#" type="formfield" />
		<cfhttpparam name="location[phone]" value="#form['location[phone]']#" type="formfield" />
		<cfhttpparam name="location[street_1]" value="#form['location[address][address_1]']#" type="formfield" />
		<cfhttpparam name="location[street_2]" value="#form['location[address][address_2]']#" type="formfield" />
		<cfhttpparam name="location[city]" value="#form['location[address][city]']#" type="formfield" />
		<cfhttpparam name="location[state]" value="#form['location[address][state]']#" type="formfield" />
		<cfhttpparam name="location[postal_code]" value="#form['location[address][zip_code]']#" type="formfield" />
		<cfhttpparam name="location[country]" value="#form['location[address][country]']#" type="formfield" />
	</cfhttp>
	< !--- cfdump var="#cfhttp#" --->

	< !--- set default values for sales super tag and capture user-provided information --->
	<cfset arrSuperTag = arrayNew(1) />
	<cfset arrayAppend(arrSuperTag, "super_tag[existing_service]=#URLEncodedFormat(form['supertags[sales first contact][existing_service]'])#") />
	<cfset arrayAppend(arrSuperTag, "super_tag[date_of_first_event]=#URLEncodedFormat(form['supertags[sales first contact][date_of_first_event]'])#") />
	<cfset arrayAppend(arrSuperTag, "super_tag[customer_comments]=#URLEncodedFormat(form['supertags[sales first contact][customer_comments]'])#") />
	<cfset arrayAppend(arrSuperTag, "super_tag[plan]=#URLEncodedFormat(form['supertags[sales first contact][plan]'])#") />
	<cfset arrayAppend(arrSuperTag, "super_tag[requested_demo]=#URLEncodedFormat(form['supertags[sales first contact][requested_demo]'])#") />
	<cfset arrayAppend(arrSuperTag, "super_tag[active]=true") />
	<cfset arrayAppend(arrSuperTag, "super_tag[paperwork_sent]=false") />
	<cfset arrayAppend(arrSuperTag, "super_tag[agreement_back]=false") />
	<cfset arrayAppend(arrSuperTag, "super_tag[announced_on_facebook]=false") />
	<cfset arrayAppend(arrSuperTag, "super_tag[buddy_check_complete]=false") />	

	<cfhttp url="#variables.root_uri#/companies/#id#/super_tags/sales.xml" method="put" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no">
		<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded" />
		<cfhttpparam type="body" value="#arrayToList(arrSuperTag, "&")#" />
	</cfhttp>
	< !--- cfdump var="#cfhttp#" --->

	< !--- create person--->
	<cfhttp url="#variables.root_uri#/people.xml" method="post" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no">
		<cfhttpparam name="person[first_name]" value="#form['contact_details[first_name]']#" type="formfield" />
		<cfhttpparam name="person[last_name]" value="#form['contact_details[last_name]']#" type="formfield" />
		<cfhttpparam name="person[company]" value="#form['company[name]']#" type="formfield" />
	</cfhttp>
	< !--- cfdump var="#cfhttp#" --->
</cfif>

Some notes about the above:

  • What’s my password? Your password is X. Or Y. Or Z. It doesn’t matter – Batchbook only authenticates you on your unique key which is sent as the username. The password can be anything.
  • If you’re new to REST APIs, I suggest enabling the CFDUMPs as they will show you how data comes and goes. Plus, you’ll see what the response headers look like (hint, they aren’t all 200 OKs).
  • Associating people with companies – there is no official way to do this. Just make sure the “company” value for the person is a string match for the company record and Batchbook will make the magic happen on their end.
  • There is basically no error checking or exception handling here – I’ve got my code wrapped up in some try/catch and I fall back to sending an email to us if all else fails. Plan for failure. At some point the API will be down or the Internet will break and you need to have a contingency plan when dealing with remote third parties.

The above code took me a day or two of toying around to get working properly. While the docs are pretty good, my experience is that API implementations never quite match their documentation. Getting super tags to work (key to our sales process) took a lot of fooling around plus some assistance from the very helpful Eric Krause at Batchbook.

Next post will be how we’re integrating the results of this sales pipeline with Email Center Pro to dynamically generate and send out contracts in preparation for our sales cycle this winter.

Everyone hates at some point
This letter was written to the Beastie Boys early in their career. Not a very flattering letter but the band went on to sell about a bazillion records anyways.

Don’t let a little bit of hate make you second guess yourself. Success usually comes at the end of a very long road of perseverance.

I’ve had my dual Italian citizenship now for almost two years now. Unfortunately at the time of our appointment, my brother lived in Florida. He flew out for the meeting only to be told he couldn’t apply with us since he was a resident in another consulate’s jurisdiction. :(

Fast forward a bit and he once again lives in California. Waiting over a year, his appointment finally came last week and we found a little bit of information I wanted to pass along. First, if an ancestor has already obtained citizenship “jure sanguinis” then the descendant’s requirements change slightly. In his case for the San Francisco consulate, he may not have needed to make an appointment at all!

Here’s what was required:

  1. His birth certificate, apostille, translation
  2. Application with details all the way up to the original citizen (great grandfather in this case)
  3. Form 2A, with a list of his residences
  4. Form 2B is NOT required since our father’s document is already on file
  5. Copy of US Passport
  6. Certified copy of his marriage license, apostille, translation
  7. Simple photocopy of his wife’s birth certificate

My brother was married last year so his wife is not eligible to apply for another two years. When she does, I think she’ll need to submit the equivalent of the first 5 items in the above list for herself since everything else will be on file.

The key thing he learned is “if one of your parents is currently Italian and registered in our anagrafe, you can contact the office of vital statistics directly“. No appointment, no waiting 1+ years, no going to the consulate! Instead, you can simply piggy back on the work of your Italian ancestor (in this case our father who applied with me a couple of years ago) and bypass the queue. If you have a parent or sibling who has already done the work, this is a huge short cut for you.

Also, in other news, the primary citizenship person at the San Francisco consulate, Anna Marie Stone, is apparently leaving after 25 years. No news on a replacement but her knowledge and expertise will certainly be missed by those applying. Buona fortuna!

A quick update after the SCCA July double regional at Thunderhill Raceway in Willows, CA! Mid-way through the season rounds 7 and 8 were held in the scorching heat of the California Central Valley. We headed into the event running neck and neck with Darin Posley and Tommy Olivier for the championship lead so these two races were important in maintaining momentum.

I qualified for the first race on pole by more than 3/10ths but was skunked on the start and fell back to third. Tommy O bounced into the lead but lost control in turn 11 which forced P2 Posley to check up and with a cleaner run I was able to take the lead on the back straight. Olivier was unfortunately collected by a car a few rows back and was out for the weekend. He is always a tough competitor so it was sad to see him done. The hotter afternoon race saw lap times a bit slower but I managed the lead and came home in first place for my third win of the season. My mechanic Thomas Barrett was on hand for his first victory lap ride-a-long (and the first time he had been on the race surface). He has played a major role in my success this season and had perma-grin after he flopped out of the car. Here’s the video from Doug Makashima’s SSM car (I’m the white car starting at the front left):

I made a silly error with qualifying on Sunday and showed up late to grid. We didn’t hit the track until the rest of the combined 53-car field was already running hot laps but I salvaged a 4th place grid position before the black flag was thrown for a rollover in turn 8. Disaster avoided and lesson learned but I had my work cut out for me with Dean Thomas on pole by ~0.8 seconds and the only car able to run in the 2:07s.

Starting in front of me was SSM car Doug Makashima. Thomas missed a shift on the start letting me bump Doug and myself into the P1 and P2 positions. Doug appeared to gift me the lead when he went into the dirt at T5 but coming back on track I made room and found myself in the marbles. Another SSM car, Matt Rose, squirted between all of us into the lead and Posley followed pulling between myself on the inside of turn 6 and Makashima on the outside. We went three-wide through 6 and 7 and there wasn’t enough room to slip a sheet of paper between us headed into turn 8 which is a high speed kink normally taken flat. Makashima, on the outside, wisely checked up and Posley and I went side by side with me gaining the advantage and assuming second. Thomas capitalized to pass Makashima into 9 and consolidate fourth before a full course caution came out for a pile-up in T1 on the start. Here’s video from Dean Thomas on the start that shows the action up close:


(I’m the white car #12)

The lead car had a slow restart and Thomas rocketed from fourth to first before we crossed the finish line. I chased Dean over the next several laps and passed him on the front straight after a great run through T14/15. I held the lead for several laps until a bobble in T2 let Dean by but I managed to keep Darin behind. The roles were reversed again and Posley and I caught back up to Thomas on the back straight. Too hot into the final corner, Thomas allowed me to run up alongside him and make a pass coming onto the front straight. You can see in his video the tight proximity without any contact. Lapped traffic cost me my gap near the end but I held on to the finish to sweep the weekend! This one was special because my wife Jennifer, who supports my racing every step of the way, was finally present for her first victory lap.

Regional #7 Qualifying (1st overall)
Regional #7 Race (1st overall)
Regional #8 Qualifying (4th OA, 3rd in SMT)
Regional #8 Race (1st overall)

With eight of thirteen races in the books, the current standings are:

Driver Total Points Points (w/ drops)
Brian Ghidinelli 316 284
Darin Posley 334 280
Tommy Olivier 252 252
David Vodden 205 205

With Tommy’s DNFs due to the spin/crash, it’s becoming a two-horse race for the regional championship this year. It’s going to come down to whomever makes a mistake so we’ll be doubling down on our prep. Next event is Labor Day weekend September 3-5 at Infineon Raceway in Sonoma for rounds 9 and 10.