📜 ⬆️ ⬇️

How to apply Twilio, Python and Google tools to automate your wedding

The author of the material tells how he managed to apply programming skills to automate the routine processes of his own wedding event.

For most people, September 3, 2016 was the most ordinary Saturday, but in my memory this date will remain forever, because it was on this day that my wife and I had a wedding.

image
')
When planning a wedding, you need to take into account many things: food, decoration and furnishings, table lamps (yes, apart from decoration), flowers, guest accommodation, transportation, entertainment, and the choice of event location. In general, when planning a wedding, you encounter a lot of unknowns, but one thing I was sure for sure: no wedding can do without a whole pile of lists, nested lists that do not end until the very end. The more lists floated before my eyes, the more often I began to think about how to improve the preparation process. It was painfully inefficient and all the work was done manually. I was sure that technology will certainly be able to help improve at least some points.

You may be surprised to learn that inviting people to a wedding is expensive (more than 380 pounds per person). First you need to send preliminary invitations with a date and a short notice, and only then - full-fledged, with a more detailed description of the event. All this, moreover, is sent by mail, which means it happens slowly. It takes a lot of time to try to “catch” the invitees and get an answer from them about whether they want to come to the feast with free food and drink (although it would seem, who does not want ?!). And, finally, sending out invitations is not environmentally friendly, because paper cards are a one-time thing, quickly forgotten and become useless.

But back to the lists. We divide guests into several groups:

  1. Those who you would like to see on the holiday
  2. Those who responded to the second invitation with a response
  3. Those who accepted the invitation
  4. Those who accepted the invitation and chose the food

But I like lists: they have certain predefined requirements that turn them into an excellent object for automation.

Message in a bottle


I was sure that all potential guests, regardless of their age, had a mobile phone, which meant that it was time for Twilio. The code given here can be safely omitted if desired, as it is always available in the corresponding GitHub repository .

SMS as a communication channel perfectly suited to my needs. I could set up a mass mailing, quickly and efficiently processing responses. By making the first working sketches of a product and considering options for a database, I tried to do something simple that I could easily share and didn’t want to spend a lot of time on appearance. As a result, I came across the gspread python library, which allowed reading from google tables and writing to them. It was not the fastest, but rather flexible option, providing an opportunity to easily access the tables and read the results.

For the first invitation, I created a table with three columns:


Having completed the entry of basic data, I drove the list through gspread , which sent an SMS to each guest owner of a mobile number:

import json import time import gspread from oauth2client.client import SignedJwtAssertionCredentials from twilio.rest import TwilioRestClient #      #     json,    json_key = json.load(open('.json')) scope = ['https://spreadsheets.google.com/feeds'] credentials = SignedJwtAssertionCredentials(json_key['client_email'], json_key['private_key'].encode(), scope) gc = gspread.authorize(credentials) wks = gc.open("wedding_guests") #     workbook- wks_attendees = wks.get_worksheet(0) #     ACCOUNT_SID = 'TWILIO_ACCOUNT_SID' AUTH_TOKEN = 'TWILIO_AUTH_TOKEN' client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) #    ,   range    for num in range(2, 60): print "sleeping for 2 seconds" time.sleep(2) #         guest_number = wks_attendees.acell('B'+str(num)).value guest_name = wks_attendees.acell('A'+str(num)).value Message_body = <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-cce">\n\n</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2709</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span><span class="pl-s"><span class="pl-pds">"</span> Save the date! <span class="pl-pds">"</span></span><span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2709</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span><span class="pl-s"><span class="pl-pds">"</span><span class="pl-cce">\n\n</span>Lauren Pang and Thomas Curtis are delighted to invite you to our wedding.<span class="pl-cce">\n\n</span>Saturday 3rd September 2016. <span class="pl-cce">\n\n</span>Colville Hall,<span class="pl-cce">\n</span>Chelmsford Road,<span class="pl-cce">\n</span>White Roding,<span class="pl-cce">\n</span>CM6 1RQ.<span class="pl-cce">\n\n</span>The Ceremony begins at 2pm.<span class="pl-cce">\n\n</span>More details will follow shortly!<span class="pl-cce">\n\n</span>Please text YES if you are saving the date and can join us or text NO if sadly, you won't be able to be with us.<span class="pl-cce">\n\n</span><span class="pl-pds">"</span></span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2B50</span><span class="pl-pds">"</span></span> <span class="pl-k">+</span> <span class="pl-s"><span class="pl-k">u</span><span class="pl-pds">"</span><span class="pl-cce">\u2764</span><span class="pl-pds">"</span></span>, if not guest_number: #      print guest_name + ' telephone number empty not messaging' wks_attendees.update_acell('E'+str(num), '0') else: print 'Sending message to ' + guest_name client.messages.create( to="+" + guest_number, #  +   e.164 from_="", #     Twillio body=message_body, ) wks_attendees.update_acell('E'+str(num), int(wks_attendees.acell('E'+str(num)).value) + 1) # increment the message count row else: # else-  print 'finished' 

And since text SMS usually looks boring, I added some unicode to revive them. Here's what the message looked like for lucky guests:

image

Next, I used Flask as a web server, made my URL request for Twilio Messaging point to / messages, and add simple if-checks to parse the answers:

 @app.route("/messages", methods=['GET', 'POST']) def hello_guest():  if "yes" in body_strip:    #      confirmation_status    wks_attendees.update_acell("F"+str(guest_confirmation_cell.row), 'Accepted')  #    « »       resp.message(u"\u2665" + "Thanks for confirming, we'll be in touch!" + u"\u2665")  # r ,    elif "no" in from_body.lower():    #    « »       wks_attendees.update_acell("F"+str(guest_confirmation_cell.row), 'Declined')    #       resp.message("Sorry to hear that, we still love you though!")  else:  #  ,        resp.message("You sent a different keyword, we need a yes or a no, you sent: "+           from_body)  return str(resp) 

imageimage

The first message was sent at 8:37 am on February 19, and the first confirmation was received a little later at 8:40. By 9:38 I had already received 23 confirmations, that is, 32% of the answers were in my pocket! 2 days after the start of mass mailing, 58% of the guests confirmed their participation. Despite the obvious success, my future wife has not yet been 100% impressed with my SMS service of wedding invitations, and I decided to add a bit more functionality to the application.

Statistics! I could create an up-to-date guest list and provide it on the first request, giving my future bride instant feedback. The code turned out to be quite simple, as I had already set up some of the simplest counters in the table before, and so it all came down to getting the content of individual cells and adding them to SMS:

 #    guest_confirmed = wks_attendees.acell('C70').value guest_unconfirmed = wks_attendees.acell('C71').value guest_no_response = wks_attendees.acell('C72').value guest_acceptance = wks_attendees.acell('C73').value elif "numbers" in from_body.lower():  #   ( - ,   )  resp.message("RSVP update:\n\nTotal Accepted: " + guest_confirmed         "\n\nTotal declined: " guest_unconfirmed "\n\nTotal no response: "+         guest_no_response + "\n\nTotal acceptance rate: " + guest_acceptance) 

An example of SMS sent by this code:

image

It looks, perhaps, not very beautiful, but very informative.

The fact that Lauren could now keep track of the automatic updates to the guest list saved us many headaches. As a result, I received from her welcome to the widespread integration of SMS and soon this tool was used in almost all processes where it was only possible. Some of the applications were obvious, for example, sending SMS notifications about the launch of a wedding site (made, by the way, on Heroku ), or working with wedding gift lists and many other decisions that I am proud of to this day.

Glorious feast


After compiling a list of invitees and collecting answers, it came to classes, which are usually set aside for later - finding out the taste preferences of guests. The first step was to send another SMS telling guests about the need to visit the website and select the categories of food served using a google-form. The form looked quite ordinary, but filled out the same file that contained the data on visitors. Thus, now we had both a table of visitors who accepted invitations, and a table that filled out the food selection form. If we were talking about some kind of ordinary automation tool, I would have to wait while the guests slowly choose their dishes, but my wedding was held with the support of Twillio, and this means that I could get an answer from the guests with minimal effort.

It was necessary to check two tables by guest name and update the status of selected dishes when new information was received. This required some additional bells and whistles in the code, but as soon as I finished with them, I could run the script on an urgent basis, receiving at the output an SMS with relevant information:

 import json import time import gspread from oauth2client.client import SignedJwtAssertionCredentials from twilio.rest import TwilioRestClient #     json,    json_key = json.load(open('')) scope = ['https://spreadsheets.google.com/feeds'] credentials = SignedJwtAssertionCredentials(json_key['client_email'],                      json_key['private_key'].encode(),                      scope) gc = gspread.authorize(credentials) wks = gc.open("")  #      wks_attendees = wks.get_worksheet(0)  #   wks_food = wks.get_worksheet(1)  #    ACCOUNT_SID = 'TWILIO_ACCOUNT_SID' AUTH_TOKEN = 'TWILIO_AUTH_TOKEN' client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN) #   . -       for num in range(2, 60):  food_guest_name = wks_food.acell('B'+str(num)).value  #        if food_guest_name:    attendees_name = wks_attendees.find(val_food_guest_name).value    attendees_name_row = wks_attendees.find(val_food_guest_name).row    menu_status = wks_attendees.acell("G"+str(attendees_name_row)).value    if food_guest_name == attendees_name:      print      if menu_status == 'Y':  #   ,          print('Skipping')      else:  #    ,           print ('Food sheet name ' + food_guest_name + 'Attendees sheet name ' + attendees_name)        #            wks_attendees.update_acell("G"+str(attendees_name_row), 'Y')    else:      print('nothing found, moving on')      wks_attendees.update_acell('E'+str(num), int(wks.acell('E'+str(num)).value) + 1)  #      else:    #           client.messages.create(from_="",  #  Twillio                to="",  #                  body="Finished processing current meal listnnGuest meals confirmed" + guest_meals_confirmed + "\n\nGuest meals unconfirmed: " + guest_meals_unconfirmed) 

Now that I had at my disposal an accurate guest list and a constantly growing list of dishes, it made sense to make these statistics publicly accessible using the main application. It only required to add the contents of the corresponding cells to the SMS reply:

 #           elif "food" in body_strip.strip():  resp.message("Guest meals decided:" + guest_meals_confirmed + "\nGuest meals undecided: " + guest_meals_unconfirmed + "\n\nMenu breakdown:\n\n" + starter_option_1 +": " + starter_option_1_amount + "\n" + starter_option_2 +": " + starter_option_2_amount + "\n" + starter_option_3 +": " + starter_option_3_amount + "\n" + main_option_1 +": " + main_option_1_amount + "\n" + main_option_2 +": " + main_option_2_amount + "\n" + main_option_3 +": " + main_option_3_amount + "\n" + dessert_option_1 + ": " + dessert_option_1_amount + "\n" + dessert_option_2 + ": " + dessert_option_2_amount) 

image

This measure turned out to be very useful, as it allowed the company serving the holiday to keep abreast of our progress and provided very practical information about those who had not yet made their choice. The next bidder for automation was the process of getting answers from guests. To do this, it was necessary to just go through the list, find in it “violators” who did not choose their own dishes, and send them messages!

 for num in range(2, 72):  #         print "sleeping for 3 seconds"  time.sleep(3)  #            wedding_guest_number = wks_attendees.acell('B'+str(num)).value  #     wedding_guest_name = wks_attendees.acell('A'+str(num)).value  #     menu_guest = wks_attendees.acell('G'+str(num)).value  if not wedding_guest_number:    print wedding_guest_name+' telephone number empty not messaging'  #   ,     .            wks_attendees.update_acell('H'+str(num), '1')  #         else:    if menu_guest == "N":  #    !      !      print 'Sending message to '+wedding_guest_name      client.messages.create(        to="+" + wedding_guest_number,        from_="",  #   Twillio        body="If you have received this message, you have not chosen your food options for Tom & Lauren's Wedding!\n\nYou can pick your choices via the website, no paper or postage required!\n\nhttp://www.yourwebsitehere.com/food"      )      wks_attendees.update_acell('H'+str(num), int(wks_attendees.acell('H'+str(num)).value) + 1)  #        else:          # else-   print 'finished' 

image

The big day was coming faster than we could have imagined. The only thing left for us to do was to send the last SMS reminding guests of the main details and the need to equip themselves with an umbrella, which will help protect themselves from a typical rainy British summer:

image

Finally


Wedding organization is never easy. At any time you may find that many aspects of the event are out of your control. Automation has definitely made my life easier by providing a direct channel of communication with guests and countless tools to keep track of their answers and remind them of the need to decide and respond. She helped us to take into our hands one of the most tedious things accompanying such events, allowed us to release a lot of time and focus on other important components of such an important event in our life.

Creating scalable solutions for complex tasks is never easy, and even one of the final versions of my application could barely cope with the tasks. Initially, I planned to develop a more comprehensive solution, with visualization of progress, voice integration, less dependent on CLI-scripts, but time gained the upper hand in this race. In general, I am pleased with how everything turned out. There is no perfect communication system. You should always use the channel that is most suitable for your audience, be it SMS , Voice , Chat , Video or semaphore .

If you want to talk about the automation of weddings, write to me on Twitter .

image

Source: https://habr.com/ru/post/402827/


All Articles