---title: Text Subscriptionslug: /guides/text-subscriptionsidebar_position: 3x-custom: ported_from_readme: true---Overview--------This guide will show you how to create a phrase-based subscription service using SignalWire and Python. The application will demonstrate how you can easily create and maintain multiple campaigns as well as their associated subscribers. The list administrator can broadcast to specific campaigns and is notified of new subscribers and removal requests via email. If you reply with stop or unsubscribe, the number will be placed on a black list.What do you need to run this code?----------------------------------View the full code on [Github](https://github.com/signalwire/guides/tree/main/Messaging/Text%20Subscription%20with%20Python).You will need the [Python SignalWire SDK](pathname:///compatibility-api/rest/overview/client-libraries-and-sdks#python) and the [MailGun API](https://www.mailgun.com/) to send an email.You will need a SignalWire phone number as well as your API Credentials (**API Token**, **Space URL**, and **Project ID**) which can all be found in an easily copyable format within the **API** tab of your SignalWire portal. How to Run Application----------------------### Build and Run on Docker1. Use our pre-built image from Docker Hub docker pull signalwire/snippets-text-subscription:python(or build your own image)1. Build your imagedocker build -t snippets-text-subscription .2. Run your imagedocker run --publish 5000:5000 --env-file .env snippets-text-subscription3. The application will run on port 5000### Build and Run NativelyTo run the application, execute export FLASK_APP=app.py then run flask run.You may need to use an SSH tunnel for testing this code if running on your local machine. – we recommend [ngrok](https://ngrok.com/). You can learn more about how to use ngrok [here](/guides/how-to-test-webhooks-with-ngrok). Step by Step Code Walkthrough-----------------------------Within the Github repository you will find 5 files:- Dockerfile - .env - campaigns.json - donotcontact.json- app.py As the Dockerfile is explained above, we will start with the .env file containing our environment variables and work our way down the list in each section. ### Setup Your Environment File1. Copy from example.env and fill in your values2. Save a new file called .envYour file should look something like this.## This is the full name of your SignalWire Space. e.g.: example.signalwire.comSIGNALWIRE_SPACE=# Your Project ID - you can find it on the API page in your Dashboard.SIGNALWIRE_PROJECT=# Your API token - you can generate one on the API page in your DashboardSIGNALWIRE_TOKEN=# The phone number youll be using for this guide. Must include the +1, SIGNALWIRE_NUMBER=# MailGun domain associated with your MailGun accountMAILGUN_DOMAIN=# MailGun token associated with your MailGun AccountMAILGUN_API_TOKEN=# Send Email From AddressEMAIL_FROM=info@yourdomain.com# Send email to address for administrator notificationsEMAIL_TO=youremail@yourdomain.com# Email subject for admin notificationsEMAIL_SUBJECT=Text Subscription Admin Notification### Setup Your Campaign File1. Edit campaign.json to suit your needs, the file is a JSON object that can handle multiple campaigns.Your file should look something like thisyaml[ { Id: 1, Name: signalwire-demo-1, Phrases: [ signalwire ], Subscribers: [ ] }, { Id: 2, Name: signalwire-demo-2, Phrases: [ original ], Subscribers: [ ] }]### Setup Your Do Not Contact File1. Edit donotcontact.json to suit your needs, the file is a JSON object and is global. This has no entries by default.Your file should look something like thisyaml{DoNotContact: []}### Configuring the CodeThe first step is to define a function send_email(body) that will utilize the MailGun API to send an email using the variables from our .env file.python# MailGun Send Emaildef send_email(body): # Post to MailGun to shoot out an email return requests.post( https://api.mailgun.net/v3/ + os.environ[MAILGUN_DOMAIN] + /messages, auth=(api, os.environ[MAILGUN_API_TOKEN]), data={from: os.environ[EMAIL_FROM], to: [os.environ[EMAIL_TO]], subject: os.environ[EMAIL_SUBJECT], text: body })Next, we will define a quick function to help us turn a JSON object into a string. python# JSON serialization helperdef set_default(obj): if isinstance(obj, set): return list(obj) raise TypeErrorNow that we have defined the two necessary functions that we will call in this process, we can move on to our routes that will handle GET/POST requests! The first route we will use is /text_inbound which will accept GET/POST requests for inbound text messages. The first part of this route is for handling **opt-out messaging**. We will open both the campaigns.json file and the donotcontact.json file, and get the body and from number parameters from the HTTP request. We will check to make sure that the number is not on the do not contact list - if it is, we will ignore the request. Then we will check if the message body includes any version of unsubscribe or remove or stop. If it does, we will add them to the do not contact list and send them an unsubscribed message. We will then use the send_email(body) function to send an email to an administrator that the subscriber has opted out of further messaging. The next part is how to handle **opt-in messaging**. Here we will loop through all of the campaigns in the campaign.json file. If the number is already subscribed, we will send them a message to let them know. If not, we will add them to our campaign.json file and send them a message to thank them for subscribing. python# Listen on route /text_inbound for inbound text messages on GET/POST requests@app.route(/text_inbound, methods=[GET, POST])def text_inbound(): # Initialize the SignalWire client client = signalwire_client(os.environ[SIGNALWIRE_PROJECT], os.environ[SIGNALWIRE_TOKEN], signalwire_space_url = os.environ[SIGNALWIRE_SPACE]) # Read campaigns from json file with open(campaigns.json) as f: campaigns = json.load(f) # Read Do Not Contact Json with open(donotcontact.json) as f: donotcontact = json.load(f) # Read params passed in by request phrase = request.values.get(Body) number = request.values.get(From) print(donotcontact) print(number) # If number is on do not contact list, ignore request for x in donotcontact[DoNotContact]: print(x[0]) if number == x[0]: return 200 # Trim the phrase provided phrase = phrase.strip() # Check phrase for STOP / UNSUBSCRIBE if phrase.lower() == stop or phrase.lower() == unsubscribe or phrase.lower() == remove: # Add number to DoNotContact file donotcontact[DoNotContact].append( { number } ) # Write updated DoNotContact to file with open(donotcontact.json, w) as f: print(donotcontact) json.dump(donotcontact, f, default=set_default) # Send receipt of unsubscribe message message = client.messages.create( from_ = os.environ[SIGNALWIRE_NUMBER], body = You have been removed from our list., to = number ) # Send email to administrator or your hook logic, unsubscribed send_email(Subscriber requested to be removed: \n + Number: + number) return 200 # Loop through all campaigns for active phrase for campaign in campaigns: if phrase in campaign[Phrases]: # If the number is in a campaign then they are already subscribed, else add them to the campaign for x in campaign[Subscribers]: if number == x[0]: # Send already subscribed message message = client.messages.create( from_ = os.environ[SIGNALWIRE_NUMBER], body = You are already subscribed to + phrase + , to = number ) else: # Add new number to campaign subscriber list campaign[Subscribers].append( { number } ) # write updated data to file with open(campaigns.json, w) as f: json.dump(campaigns, f, default=set_default) # Send message message = client.messages.create( from_ = os.environ[SIGNALWIRE_NUMBER], body = Thank you for subscribing to + phrase + , to = number ) # Send email to administrator or your hook logic send_email(New subscriber to campaign: + phrase + \n + Number: + number) return 200The next endpoint /broadcast_msg can be called by the list administrator in order to send out messages. When this endpoint is called along with the code and message parameters, it will open the campaign.json file, loop through the subscribers, and send out the messages. python# Listen for route/requests at endpoint@app.route(/broadcast_msg, methods=[GET,POST])def broadcast_msg(): # Initialize the SignalWire client client = signalwire_client(os.environ[SIGNALWIRE_PROJECT], os.environ[SIGNALWIRE_TOKEN], signalwire_space_url = os.environ[SIGNALWIRE_SPACE]) # Read phrase param, that represents campaign subscribers group_code = request.values.get(code) # Read message param, that represents message to be sent message = request.values.get(message) # Read campaigns from json file with open(campaigns.json) as f: campaigns = json.load(f) # Loop through subscribers, and send messages for number in campaigns[Subscribers]: message = client.messages.create( from_ = os.environ[SIGNALWIRE_NUMBER], body = message, to = number ) return 200Wrap Up-------If youre running messaging campaigns as a business, one of the most important rules to follow is respecting the opt-ins or opt-outs from customers. This guide has shown one possible way to implement the process of text subscription as well as the maintenance of subscriber lists. Required Resources: - [Github Repo](https://github.com/signalwire/guides/tree/main/Messaging/Text%20Subscription%20with%20Python)- [Python SignalWire SDK](pathname:///compatibility-api/rest/overview/client-libraries-and-sdks#python) - [MailGun API](https://www.mailgun.com/)- [SMS/MMS Best Practices - How to Ensure Message Delivery](/guides/sms-best-practices-how-to-ensure-message-delivery) Sign Up Here------------If you would like to test this example out, you can create a SignalWire account and space [here](https://m.signalwire.com/signups/new?s=1).Please feel free to reach out to us on our [Community Slack](https://signalwire.community/) or create a Support ticket if you need guidance!