How to manage your Contacts using Reflex (Pynecone)

10 min read

Ever get an invitation to a hackathon, a party or (yikes) a job interview, only to find it weeks or months later because it went to the wrong email or voicemail box? If you rely on an old email or cellphone number, this could happen to you! It’s critical to keep your contacts up to date so you don’t miss opportunities. How about we use Nylas to build our interface for managing contact info? We’re going to make a Contact Manager to update our contacts.

To build our Contact Manager, we’re going to use Reflex, a Python-based Web-UI Framework.

Is your system ready?

If you already have the Nylas Python SDK installed and your Python environment configured, skip to the next section.

If not, read the post How to Send Emails with the Nylas Python SDK, where I cover the basic steps to set up your environment.

What are we going to talk about?

What our application will look like

When you run your Reflex Contact Manager, it will display five contacts that belong to a group that you specify. Of course, with Nylas you can display all contacts without worrying if they belong to a group or not. It’s your choice:

Reflex contacts manager

Next to each contact, we’ll add an update button that shows all the contact detail fields.

Select a contact to be updated

To update a contact, edit any of the fields and click Submit to save the change.

Updating the contact

After that, the contact shows the change when you view it again.

The contact was updated

One important limitation to mention – I haven’t found a way to enable notifications when the contact gets updated, but we can see that it was because the updated job title shows up in the contact card.

Installing the required packages

Besides the Nylas and Dotenv packages, we need to install the Reflex and the Pandas packages:

$ pip3 install reflex
$ pip3 install pandas

That’s it! Nothing too complex or complicated.

Coding our Reflex Contact Manager

And now it’s coding time. Go to your terminal window and type the following:

$ mkdir reflex_contacts_manager
$ cd reflex_contacts_manager
$ pc init

This creates some files and libraries, but importantly it creates a new folder called reflex_contacts_manager. Inside this folder, you’ll find the file reflex_contacts_mananer.py, which we need to update. The code in this file is big, so we’ll split our discussion into more digestible sections:

In this section, we load all libraries and fetch the list of contacts:

# Import your dependencies
from rxconfig import config
import reflex as rx
from dotenv import load_dotenv
import os
from nylas import APIClient  # type: ignore
import pandas as pd

# Load the app configuration
filename = f"{config.app_name}/{config.app_name}.py"

# Load your env variables
load_dotenv()

# Initialize an instance of the Nylas SDK using the client credentials
nylas = APIClient(
    os.environ.get("CLIENT_ID"),
    os.environ.get("CLIENT_SECRET"),
    os.environ.get("ACCESS_TOKEN"),
)

# Get a list of contacts, from a particular group
# limiting to 5 contacts only
def get_contacts():
    ids = []
    full_names = []	
    # Call the contacts endpoint
    contacts = nylas.contacts.where(source = 'address_book', limit = 5, group = "517v55haghlcvnuu7lcm4f7k8")
    # Loop the contacts
    for contact in contacts:
        # Append them to lists
        ids.append(contact.id)
        full_names.append(contact.given_name + " " + contact.surname)
    # Create a dictionary 
    data = {'ids' : ids, 'full_names' : full_names}
    # Create a Pandas Dataframe
    df = pd.DataFrame(data)
    return df

This section handles all the application variables, gets the details for each contact, binds the changes on each field and handles the contact updates:

# This class will handle the list of contacts
class Contact(rx.Model, table = True):
    ids : str 
    full_names : str
    
    def __init__(self, ids, full_names):
        self.ids = ids
        self.full_names = full_names

# This handles all the variables in our application
class State(rx.State):
    form_data: dict = {}
    contact_id : str = ""
    contact_name : str = ""
    contact_surname : str = ""
    contact_company : str = ""
    contact_jobtitle : str = ""
    contact_email : str = ""
    contact_profilepic : str = ""
    contact_emailtype : str = ""
    contact_country : str = ""
    contact_street : str = ""
    contact_city : str = ""
    contact_state : str = ""
    contact_postal_code : str = ""
    contact_phone_type : str = ""
    contact_address_type : str = ""
    contact_phone_number : str = ""
    
    # Fetch all contacts
    _contacts = get_contacts()
    # Create a contacts list
    contacts:list[Contact] = []
    for i in range(0, len(_contacts)):
		# Fill up the list
        contacts.append(Contact(_contacts.iloc[i]['ids'], _contacts.iloc[i]['full_names']))

    # Get details for each selected contact
    def get_contact_details(self, contact_id : str):
        contact = nylas.contacts.get(contact_id)
        file_name = f'{contact_id}.png'
        # Download the contact picture
        picture = contact.get_picture()
        profile_image = open(f'assets/{file_name}', 'wb')
        profile_image.write(picture.read())
        profile_image.close()
        # Store contact details on local variables
        self.contact_id = contact_id
        self.contact_profilepic = file_name
        self.contact_name = contact.given_name
        self.contact_surname = contact.surname
        self.contact_company = contact.company_name
        self.contact_jobtitle = contact.job_title if contact.job_title != None else "Empty"
        self.contact_email = list(contact.emails.values())[0][0]
        try:
            self.contact_phone_number = list(contact.phone_numbers.values())[0][0]
            self.contact_phone_type = list(contact.phone_numbers)[0]
        except Exception as e:
            print(f'{e}')
            self.contact_phone_number = "123"
            self.contact_phone_type = "Mobile"
        try:
            self.contact_address_type = list(contact.physical_addresses.values())[0][0]["type"]
            self.contact_country = list(contact.physical_addresses.values())[0][0]["country"]
            self.contact_street = list(contact.physical_addresses.values())[0][0]["street_address"]
            self.contact_city = list(contact.physical_addresses.values())[0][0]["city"]
            self.contact_state = list(contact.physical_addresses.values())[0][0]["state"]
            self.contact_postal_code = list(contact.physical_addresses.values())[0][0]["postal_code"] 
        except Exception as e:
           print(f'{e}')
           self.contact_address_type = ""
           self.contact_country = ""
           self.contact_street = ""
           self.contact_city = ""
           self.contact_state = ""
           self.contact_postal_code = ""   

    # These functions help us bind
    # the updated value with the UI
    def set_name(self, name: str):
        self.contact_name = name
        
    def set_surname(self, surname: str):
        self.contact_surname = surname        
        
    def set_company(self, company: str):
        self.contact_company = company

    def set_jobtitle(self, jobtitle: str):
        self.contact_jobtitle = jobtitle

    def set_email(self, email: str):
        self.contact_email = email
        
    def set_country(self, country: str):
        self.contact_country = country  

    def set_street(self, street: str):
        self.contact_street = street
        
    def set_city(self, city: str):
        self.contact_city = city
        
    def set_state(self, state: str):
        self.contact_state = state             

    def set_postal_code(self, postal_code: str):
        self.contact_postal_code = postal_code

    def set_phone_number(self, phone_number: str):
        self.contact_phone_number = phone_number

    # When we press Submit
    def handle_submit(self, form_data: dict):
        # form_data is a dictionary that contains
        # all the form variables
        self.form_data = form_data
        # Update values if any
        self.form_data['id'] = self.contact_id
        self.form_data['contact_name'] = self.contact_name
        self.form_data['surname'] = self.contact_surname
        self.form_data['company_name'] = self.contact_company
        self.form_data['job_title'] = self.contact_jobtitle
        self.form_data['email'] = self.contact_email
        self.form_data['email_type'] = self.contact_emailtype
        self.form_data['phone_number'] = self.contact_phone_number
        self.form_data['country'] = self.contact_country
        self.form_data['street_address'] = self.contact_street
        self.form_data['city'] = self.contact_city
        self.form_data['state'] = self.contact_state
        self.form_data['postal_code'] = self.contact_postal_code
        self.form_data['phone_type'] = self.contact_phone_type
        self.form_data['address_type'] = self.contact_address_type
        contact = nylas.contacts.get(self.form_data['id'])
        contact.given_name = self.form_data['contact_name']
        contact.surname = self.form_data['surname']
        contact.company_name = self.form_data['company_name']
        contact.job_title = self.form_data['job_title']
        contact.emails[self.form_data['email_type']] = [self.form_data['email']]
        contact.phone_numbers[self.form_data['phone_type']]  = [self.form_data['phone_number']]
        if self.form_data['street_address'] is not None or self.form_data['street_address'] != '':
            contact.physical_addresses['Work'] = [{
                    'format': 'structured',
                    'city': self.form_data['city'],
                    'country': self.form_data['country'],
                    'state': self.form_data['state'],
                    'postal_code': self.form_data['postal_code'],
                    'type': self.form_data['address_type'],
                    'street_address': self.form_data['street_address']}]        
        try:
            # Try to save the contact
            contact.save()
        except Exception as e:
            print(f'{e}')

The last section belongs to the UI of our application:

# This is our UI
def index() -> rx.Component:
    return rx.center(
        rx.vstack(
            rx.vstack(
                rx.hstack(
                    rx.heading("Contacts"),
                ),
    rx.flex(
    rx.box(
        rx.table_container(
            rx.table(
                rx.thead(
                    rx.tr(
                        rx.th("Names"),
                    )
                ),
                rx.tbody(
                    rx.foreach(State.contacts, lambda contact:
						rx.tr(
						    rx.td(contact.full_names),
						    rx.td(
						        # The update button next to each contact 
						        rx.button(
						            "update",
						            on_click = lambda: State.get_contact_details(contact.ids),
						            bg = "red",
						            color = "white",
						        )
						    )
						)
                )
            )
        )
    ),
    bg="#F7FAFC ",
    border="1px solid #ddd",
    border_radius="25px",
    ),
    # This form holds all the contact information
    # that we can update
    rx.form(
        rx.center(
            rx.image(src=State.contact_profilepic, width="100px", height="auto"), 
        ),
        rx.hstack(
            rx.text("First Name:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_name,
	            on_change = State.set_name,
	            id = "contact_name",
	        ),
	    ),
	    rx.hstack(
	        rx.text("Last Name:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_surname,
	            on_change = State.set_surname,
	            id = "surname",
	        ),
	    ),
	    rx.hstack(
	        rx.text("Company Name:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_company,
	            on_change = State.set_company,
	            id = "company_name",
	        ),
	    ),
	    rx.hstack(
	        rx.text("Job Title:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_jobtitle,
	            on_change = State.set_jobtitle,
	            id = "job_title",
	        ),
	    ),
	    rx.hstack(
	        rx.text("Email:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_email,
	            on_change = State.set_email,
	            id = "email",
	        ),
	    ),
	    rx.hstack(
	        rx.text("Phone Number:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_phone_number,
	            on_change = State.set_phone_number,
	            id = "phone_number",
	        ),
	    ),	    
	    rx.hstack(
	        rx.text("Country:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_country,
	            on_change = State.set_country,
	            id = "country",
	        ),
	    ),
	    rx.hstack(
	        rx.text("Address:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_street,
	            on_change = State.set_street,
                id = "street_address",
	        ),
	    ),
	    rx.hstack(
	        rx.text("City:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_city,
	            on_change = State.set_city,
	            id = "city",
	        ),
	    ),
	    rx.hstack(
	        rx.text("State:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_state,
	            on_change = State.set_state,
	            id = "state",
	        ),
	    ),
	    rx.hstack(
	        rx.text("Postal Code:", as_ = "b"),
	        rx.input(
	            placeholder = State.contact_postal_code,
	            on_change = State.set_postal_code,
	            id = "postal_code",
	        ),
	    ),	    
	    rx.center(
	       rx.hstack(
	        rx.button(
                "Submit",
                type_="submit",
				bg = "red",
				color = "white",
            ),
          ),   
        ),
        on_submit=State.handle_submit,
    ),
    bg="#F7FAFC ",
    border="1px solid #ddd",
    border_radius="25px",    
)
)
)
)

# Add state and page to the app.
app = rx.App(state=State)
app.add_page(index)
app.compile()

Running our Reflex Contact Manager

To run our application, just type the following in your terminal window:

$ pc run
Running our Reflex contacts manager

Our application runs on port 3000 of localhost, so open up your favourite browser and type:

http://localhost:3000/

We will be ready to use manage our contacts using Reflex.

If you want to learn more about our Contacts APIs, see our documentation Contacts API Overview.

You can sign up Nylas for free and start building!

Don’t miss the action, join our LiveStream Coding with Nylas!

Related resources

How to build a CRM in 3 sprints with Nylas

What is a CRM? CRM stands for Customer Relationship Management, and it’s basically a way…

How to create an appointment scheduler in your React app

Learn how to create an appointment scheduler in your React app using Nylas Scheduler. Streamline bookings and UX with step-by-step guidance.

Beyond APIs: Designing elegant, smart Web Components

Dive into the world of smart Web Components for advanced API integration solutions. Design elegant, customizable elements to elevate user experiences.