HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/buyercall_forms/buyercall/buyercall/blueprints/phonenumbers/tasks.py
import traceback
import logging as log
import time
import boto
import uuid
import boto3
import requests
from io import BytesIO
from buyercall.lib.util_crypto import AESCipher
import redis
from flask import url_for, current_app as app

import pytz
from flask_babel import lazy_gettext as _
from sqlalchemy.orm import load_only
from buyercall.app import create_celery_app
from buyercall.lib.flask_mailplus import _try_renderer_template
from buyercall.lib.util_ses_email import send_ses_email
from buyercall.lib.util_twilio import (
    CallStorage, subaccount_client, InboundCallState as State,
)
from buyercall.lib.util_webhooks import WebhookUtil
from buyercall.extensions import db
from buyercall.blueprints.phonenumbers.models import Phone
from buyercall.blueprints.leads.models import Lead
from buyercall.blueprints.agents.models import Agent
from buyercall.blueprints.user.models import User
from buyercall.blueprints.contacts.models import Contact
from buyercall.blueprints.partnership.models import PartnershipAccount, Partnership
from buyercall.blueprints.phonenumbers.routing import (
    get_available_agents, get_routing_agents,
    schedule_callback, get_agent_number
)


HOURS = 3600
DAYS = 86400  # The length of a day in seconds
AGENT_TIMEOUT = 23  # The agent calls will be timed out/dropped in the seconds specified if no answer from agent

celery = create_celery_app(app)
webhooker = WebhookUtil()


@celery.task
def call_simultaneous(agent_ids, lead_id, routing, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    log.debug('Parallel call task started.')
    lead = Lead.query\
        .join(Lead.partnership_account)\
        .join(PartnershipAccount.partnership)\
        .filter(Lead.id == lead_id)\
        .one()

    # import partnership information to get partnership id
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    # Get the partner id to get relevant twilio credentails with twilio client
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    agents = get_available_agents(agent_ids, expand_groups=True)

    if not agents:
        # TODO: Notify lead
        log.warning('No agents are available to service lead {}.'.format(
            lead_id
        ))
        lead.status = 'missed'
        db.session.commit()
        # send_notify_email(lead, lead.widget)

    storage = CallStorage(redis_db, lead_id)

    log.debug('Calling {} agents...'.format(len(agents)))
    call = None

    subscription = lead.partnership_account.subscription
    if not subscription:
        subscription = lead.partnership_account.partnership.subscription

    storage.is_group = False
    twilio_client = subaccount_client(subscription.twilio_subaccount_sid, partner.id)

    for agent in agents:
        try:
            agent_number, extension = get_agent_number(agent, routing)
            digits = 'wwww{}'.format(extension) if extension else None

            with storage.lock():
                if storage.state in [
                    State.NEW, State.LEAD_ONHOLD, State.CALLBACK_PROMPT
                ]:
                    call = twilio_client.calls.create(
                        to=agent_number,
                        from_=lead.inbound.phonenumber,
                        url=url_for(
                            'twilio_inbound.agent_digit_prompt',
                            lead_id=lead_id,
                            agent_id=agent.id,
                            _external=True,
                            _scheme='https'
                        ),
                        status_callback=url_for(
                            'twilio_inbound.agent_inbound_call_status',
                            dial_digit=str(routing['dialDigit']),
                            agent_id=agent.id,
                            lead_id=lead_id,
                            _external=True,
                            _scheme='https'
                        ),
                        status_callback_event=['ringing', 'answered', 'completed'],
                        send_digits=digits,
                        timeout=AGENT_TIMEOUT
                    )
                    storage.push_agent_call(agent.id, call.sid)

                    if agent and lead:
                        # from buyercall.blueprints.contacts.models import Contact
                        contact = Contact.query.filter(Contact.id == lead.contact_id).first()
                        if contact and contact.agent_id != agent.id:
                            contact.agent_id = agent.id
                            contact.agent_assigned = agent.full_name
                            db.session.commit()
                            from buyercall.blueprints.mobile.utils import send_agent_push_notification
                            send_agent_push_notification(contact)
        except Exception as e:
            log.warning(traceback.format_exc())
            log.warning('Error calling agent {}...'.format(
                agent.id
            ))

    if call is None:
        # None of the calls got through
        lead.status = 'missed'
        db.session.commit()


@celery.task
def redirect_to_callback_prompt(lead_id, **kwargs):
    """
    If nobody has picked up in 30 seconds, redirect lead to a callback
    prompt. See https://trello.com/c/Gbwmo0Ox/ for discussion.
    """
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    lead = Lead.query.join(Lead.partnership_account).join(PartnershipAccount.partnership).filter(
        Lead.id == lead_id
    ).first()

    # import partnership information to get partnership id
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    # Get the partner id to get relevant twilio credentails with twilio client
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    subscription = lead.partnership_account.subscription
    if not subscription:
        subscription = lead.partnership_account.partnership.subscription

    twilio_client = subaccount_client(subscription.twilio_subaccount_sid, partner.id)

    storage = CallStorage(redis_db, lead_id)
    with storage.lock():
        if storage.state == State.LEAD_ONHOLD:
            twilio_client.calls.route(storage.call_sid, url=url_for(
                'twilio_inbound.lead_callback_prompt',
                lead_id=lead_id,
                _external=True,
                _scheme='https'
            ))
            storage.state = State.CALLBACK_PROMPT


@celery.task
def callback_lead(lead_id, **kwargs):
    """ Scheduled callbacks for missed leads (1, 6, 12 hours)
    """
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])
    lead = Lead.query.join(Lead.partnership_account).join(PartnershipAccount.partnership).join(Lead.inbound).filter(
        Lead.id == lead_id
    ).first()

    if not lead:
        return

    # import partnership information to get partnership idt
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    # Get the partner id to get relevant twilio credentails with twilio client
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    storage = CallStorage(redis_db, lead_id)
    routing = storage.routing
    if not routing:
        log.warning('Callback routing missing; canceling callback')
        return
    if storage.state != State.CALL_ME_BACK:
        log.warning('Lead state {} instead of CALL_ME_BACK; canceling.'.format(
            storage.state
        ))
        return

    routing['callOrder'] = 'simultaneous'
    storage.routing = routing

    subscription = lead.partnership_account.subscription
    if not subscription:
        subscription = lead.partnership_account.partnership.subscription

    twilio_client = subaccount_client(subscription.twilio_subaccount_sid, partner.id)
    storage.inc_callback_cnt()

    agents = get_routing_agents(routing)
    if not agents:
        log.warning('No available agents to serve lead {}!'.format(lead_id))
        schedule_callback(storage)
        return

    storage.state = State.CALLBACK
    storage.is_group = False

    log.debug('Calling {} agents...'.format(len(agents)))
    call = None

    webhooker.trigger_generic_webhook('operational_start_call', lead.id)

    for agent in agents:
        try:
            agent_number, extension = get_agent_number(agent, routing)
            digits = 'wwww{}'.format(extension) if extension else None

            with storage.lock():
                if storage.state == State.CALLBACK:
                    call = twilio_client.calls.create(
                        to=agent_number,
                        from_=lead.inbound.phonenumber,
                        url=url_for(
                            'twilio_inbound.agent_callback_lead',
                            lead_id=lead_id,
                            agent_id=agent.id,
                            _external=True,
                            _scheme='https'
                        ),
                        status_callback=url_for(
                            'twilio_inbound.agent_callback_call_status',
                            dial_digit=str(routing['dialDigit']),
                            agent_id=agent.id,
                            lead_id=lead_id,
                            _external=True,
                            _scheme='https'
                        ),
                        status_callback_event=['answered', 'completed'],
                        send_digits=digits,
                        timeout=AGENT_TIMEOUT
                    )
                    storage.push_agent_call(agent.id, call.sid)
        except Exception as e:
            log.warning(traceback.format_exc())
            log.warning('Error calling agent {}...'.format(
                agent.id
            ))

    if call is None:
        # None of the calls got through
        lead.status = 'missed'
        db.session.commit()
        schedule_callback(storage)


@celery.task
def connect_lead_to_agent(lead_id, agent_id, call_settings, **kwargs):
    """ Scheduled callbacks for missed leads (1, 6, 12 hours)
    """
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    from ..agents.models import Agent

    lead = Lead.query.join(Lead.partnership_account).join(PartnershipAccount.partnership).join(Lead.inbound).filter(
        Lead.id == lead_id
    ).first()
    if not lead:
        return

    # import partnership information to get partnership id
    partner_account = PartnershipAccount.query \
        .filter(PartnershipAccount.id == lead.partnership_account_id).first()
    # Get the partner id to get relevant twilio credentails with twilio client
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    storage = CallStorage(redis_db, lead_id)
    storage.routing = call_settings
    storage.manual_call = 'True'
    storage.routing_config = {}
    subscription = lead.partnership_account.subscription
    if not subscription:
        subscription = lead.partnership_account.partnership.subscription

    twilio_client = subaccount_client(subscription.twilio_subaccount_sid, partner.id)

    agent = Agent.query.filter(Agent.id == agent_id).first()
    if not agent:
        log.error('Cannot find agent with ID {}!'.format(lead_id))
        return

    storage.state = State.CALLBACK
    storage.is_group = False
    log.debug('Calling agent with ID {}...'.format(agent_id))
    call = None

    try:
        agent_number, extension = get_agent_number(agent, call_settings)
        digits = 'wwww{}'.format(extension) if extension else None

        with storage.lock():
            if storage.state == State.CALLBACK:
                call = twilio_client.calls.create(
                    to=agent_number,
                    from_=lead.inbound.phonenumber,
                    url=url_for(
                        'twilio_inbound.agent_callback_lead',
                        lead_id=lead_id,
                        agent_id=agent.id,
                        _external=True,
                        _scheme='https'
                    ),
                    status_callback=url_for(
                        'twilio_inbound.agent_callback_call_status',
                        agent_id=agent.id,
                        lead_id=lead_id,
                        _external=True,
                        _scheme='https'
                    ),
                    status_callback_event=['answered', 'completed'],
                    send_digits=digits,
                    timeout=AGENT_TIMEOUT
                )
                storage.push_agent_call(agent.id, call.sid)
    except Exception as e:
        log.warning(traceback.format_exc())
        log.warning('Error calling agent {}...'.format(
            agent.id
        ))

    if call is None:
        # None of the calls got through
        lead.status = 'missed'
        db.session.commit()


@celery.task
def call_me_back(**kwargs):
    """
    Task which executes every minute, and tries to call back leads which
    have asked to be called back soon.
    """
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    for lead_id in redis_db.hkeys('CALL_ME_BACK'):
        time_added = float(redis_db.hget('CALL_ME_BACK', lead_id))
        if time.time() - time_added > 2 * HOURS:
            log.info('Lead {}, added at {}, is probably not waiting for our '
                     'callback anymore. Removing.'.format(lead_id, time_added))
            redis_db.hdel('CALL_ME_BACK', lead_id)
            continue
        lead = Lead.query.join(Lead.partnership_account).join(PartnershipAccount.partnership).join(Lead.inbound).filter(
            Lead.id == lead_id
        ).first()
        if not lead:
            log.warning(
                'Lead {} not found - removing from callback list.'.format(
                    lead_id))
            redis_db.hdel('CALL_ME_BACK', lead_id)
            continue

        # import partnership information to get partnership id
        partner_account = PartnershipAccount.query \
            .filter(PartnershipAccount.id == lead.partnership_account_id).first()
        # Get the partner id to get relevant twilio credentails with twilio client
        partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

        subscription = lead.partnership_account.subscription
        if not subscription:
            subscription = lead.partnership_account.partnership.subscription

        twilio_client = subaccount_client(subscription.twilio_subaccount_sid, partner.id)

        storage = CallStorage(redis_db, lead_id)
        storage.state = State.CALLBACK
        storage.is_group = False
        routing = storage.routing

        agents = get_routing_agents(routing)
        if not agents:
            log.warning(
                'No available agents to serve lead {}!'.format(lead_id)
            )
            continue

        log.debug('Calling {} agents...'.format(len(agents)))
        call = None

        for agent in agents:
            try:
                agent_number, extension = get_agent_number(agent, routing)
                digits = 'wwww{}'.format(extension) if extension else None

                # Did any agent pick up?
                with storage.lock():
                    if storage.state == State.CALLBACK:
                        call = twilio_client.calls.create(
                            to=agent_number,
                            from_=lead.inbound.phonenumber,
                            url=url_for(
                                'twilio_inbound.agent_callback_lead',
                                lead_id=lead_id,
                                agent_id=agent.id,
                                _external=True,
                                _scheme='https'
                            ),
                            status_callback=url_for(
                                'twilio_inbound.agent_callback_call_status',
                                dial_digit=str(routing['dialDigit']),
                                agent_id=agent.id,
                                lead_id=lead_id,
                                _external=True,
                                _scheme='https'
                            ),
                            status_callback_event=['answered', 'completed'],
                            send_digits=digits,
                            timeout=AGENT_TIMEOUT,
                        )
                        storage.push_agent_call(agent.id, call.sid)
            except Exception as e:
                log.warning(traceback.format_exc())
                log.warning('Error calling agent {}...'.format(
                    agent.id
                ))


@celery.task
def send_phonenumber_email(phonenumber, email, partnership_account_id, **kwargs):

    partner_account = PartnershipAccount.query.filter(PartnershipAccount.id == partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    partnership_name = partner.name
    partnership_logo = partner.logo

    ctx = {'phonenumber': phonenumber, 'company': partnership_name, 'partner_logo': partnership_logo}

    # Render html template for email
    tn_email_template = _try_renderer_template('mail/new_number', ext='html', **ctx)
    send_ses_email(recipients=[email],
                   p_id=partner.id,
                   subject='New Phone Number Notification',
                   html=tn_email_template
                   )


@celery.task
def tw_upload_recording(lead_id, r_url, partner_name, **kwargs):
    """ After the call, grab the first recording from Twilio, if any, and
        upload to S3.
        """
    lead = Lead.query.filter(Lead.id == lead_id).first()
    r = r_url
    f = '{}.wav'.format(r)
    fr = requests.get(f)

    log.debug('The recording url is {}'.format(f))

    if r == 0:
        log.warning('No recordings found for lead {}!'.format(lead.id))
        return
    hex_value = uuid.uuid4().hex
    try:
        key = '{}_{}/{}_{}_{}'.format(
            partner_name, lead.partnership_account_id, lead.id, 'recording', hex_value)
        log.debug('The recording key is {}'.format(key))
    finally:
        log.debug('Could not set key for recording url')

    s3 = boto3.client(
        's3',
        aws_access_key_id=celery.conf['AMAZON_ACCESS_KEY'],
        aws_secret_access_key=celery.conf['AMAZON_SECRET_KEY']
    )

    s3.put_object(Body=BytesIO(fr.content),
                  Bucket=celery.conf['RECORDING_BUCKET'],
                  Key=key,
                  Metadata={'Content-Type': 'audio/wav'},
                  ACL='private')

    log.info('Uploaded file {}'.format(key))

    # Update recording link
    lead = Lead.query.options(
        load_only("id", "recording_url")
    ).filter(Lead.id == lead_id).first()
    lead.recording_url = '{}::{}'.format(celery.conf['RECORDING_BUCKET'], key)
    db.session.commit()
    log.info('Lead {} have been updated with recording url {}'.format(lead.id, lead.recording_url))

    webhooker.trigger_generic_webhook('operational_end_call', lead.id)


@celery.task
def add_tw_contact(phonenumber, firstname, lastname, callername, partnership_account_id, **kwargs):
    # Query the leads table to retrieve the latest lead that was added
    latest_lead = Lead.query.filter(Lead.phonenumber == phonenumber).order_by(Lead.created_on.desc()).first()
    log.info('latest lead: {}'.format(latest_lead))

    # Query contacts to see if lead already exist in the contacts table
    contact = Contact.query.filter(
        Contact.phonenumber_1 == latest_lead.phonenumber
    ).filter(Contact.partnership_account_id == partnership_account_id).first()

    if contact:
        log.info('a lead exist with number {}'.format(contact.phonenumber_1))
        contact.updated_on = latest_lead.updated_on
        contact.caller_id = callername
        log.info('The updated date is: {}'.format(latest_lead.updated_on))
        db.session.commit()
    else:
        contact = Contact(
            firstname=firstname,
            lastname=lastname,
            caller_id=callername,
            phonenumber_1=phonenumber,
            email='',
            partnership_account_id=partnership_account_id,
        )
        db.session.add(contact)
        db.session.commit()
        log.info('The contact has been added')

    new_contact = Contact.query.filter(
        Contact.phonenumber_1 == latest_lead.phonenumber
    ).filter(Contact.partnership_account_id == partnership_account_id).first()
    if new_contact:
        latest_lead.contact_id = new_contact.id
        db.session.commit()
    else:
        log.info('no lead contact exist')


@celery.task
def send_email(lead_id, **kwargs):
    # Declare redis url
    redis_db = redis.StrictRedis(host=celery.conf['REDIS_CONFIG_URL'], port=celery.conf['REDIS_CONFIG_PORT'])

    from ..widgets.models import split_emails

    lead = Lead.query.filter(Lead.id == lead_id).first()

    storage = CallStorage(redis_db, lead.id)
    if storage.manual_call:
        app.logger.debug('Notification email suppressed.')
        return

    inbound = Phone.query.filter(
        Phone.id == lead.inbound_id,
        Phone.partnership_account_id == lead.partnership_account_id
    ).first()
    if not inbound:
        return

    notifications = inbound.notifications
    routings = inbound.routing_config

    if notifications and ('notifyLeads' not in notifications
                          or ('notifyLeads' in notifications
                              and notifications['notifyLeads'] == 'none')):
        return

    # Send eventual notifications
    emails = []
    adf_emails = []

    user_email = None
    user = User.query.filter(
        User.partnership_account_id == lead.partnership_account_id,
        User.active,
        User.is_deactivated.is_(False),
        User.role == 'admin'
    ).first()
    if user:
        user_email = user.email

    agent_ids = [
        a['id'] for a in inbound.routing_config['defaultRouting']['agents']
    ]
    agent_emails = [
        a.email
        for a in Agent.query.options(load_only('email')).filter(
            Agent.partnership_account_id == lead.partnership_account_id,
            Agent.id.in_(agent_ids)
        ).all()
    ]

    partner_account = PartnershipAccount.query.filter(PartnershipAccount.id == lead.partnership_account_id).first()
    partner = Partnership.query.filter(Partnership.id == partner_account.partnership_id).first()

    if notifications['notifyLeads'] == 'missed' and lead.status == 'missed':
        emails.extend(split_emails(notifications['notifyMissedCustom']))
        if notifications['notifyMissedAgents']:
            emails.extend(agent_emails)
        if notifications['notifyMissedMe'] and user_email:
            emails.append(user_email)
    if notifications['notifyLeads'] == 'all':
        emails.extend(split_emails(notifications['notifyAllCustom']))
        if notifications['notifyAllAgents']:
            emails.extend(agent_emails)
        if notifications['notifyAllMe'] and user_email:
            emails.append(user_email)

    if not emails:
        app.logger.warning('Nowhere to send {} lead notification!'.format(
            lead.status
        ))
        return

    ctx = vars(lead)
    eastern = pytz.timezone('US/Eastern')
    ctx['created_on'] = lead.created_on.astimezone(eastern).strftime('%c')
    ctx['updated_on'] = lead.updated_on.astimezone(eastern).strftime('%c')
    ctx['adf_updated_on'] = lead.interaction_time
    ctx['partner_logo'] = partner.logo
    ctx['company'] = partner.name

    if partner.partner_url:
        ctx['lead_contact_url'] = partner.partner_url + '/contacts/contact/' + str(lead.contact_id)
    else:
        ctx['lead_contact_url'] = url_for('contacts.contact_lead_page', id=lead.contact_id, _external=True, _scheme='https')

    if lead.firstname:
        ctx['caller_name_label'] = 'Caller Name:'
        ctx['caller_name'] = ''.join((lead.firstname,' ',lead.lastname))

    if lead.caller_id:
        ctx['caller_id_label'] = 'Caller Id:'
        ctx['caller_id'] =lead.caller_id

    log.info('The lead recording url is {}'.format(lead.recording_url))
    if lead.recording_url:
        ctx['vm_div_style'] = 'block'
    else:
        ctx['vm_div_style'] = 'none'

    if lead.source:
        ctx['call_source'] = lead.source

    if lead.inbound:
        ctx['reference'] = lead.inbound.routing_config.get(
            'hiddenInformation', ''
        )

    try:
        app.logger.debug('The email list are {}'.format(emails))
        if routings.get('notifyAdf'):
            if routings['notifyAdfCustom'] is not None and routings['notifyAdfCustom'] is not '':
                multi_adf_emails = split_emails(routings['notifyAdfCustom'])

                if multi_adf_emails is not None:
                    adf_emails.extend(multi_adf_emails)
                else:
                    adf_emails.append(routings['notifyAdfCustom'])

            ctx['lead_comments'] = 'Call status: ' + lead.status
            ctx['receiver'] = ''
            agent_name = lead.agent_name
            friendly_name = lead.source
            phonenumber = lead.inbound.phonenumber
            dealership = partner_account.name
            if dealership:
                ctx['vendor_name'] = dealership
            else:
                ctx['vendor_name'] = ''
            call_lead_id = lead.id
            if call_lead_id:
                ctx['call_lead_id'] = call_lead_id
            else:
                ctx['call_lead_id'] = ''

            if agent_name is not None and agent_name is not '' and len(agent_name) > 2:
                ctx['lead_comments'] = ctx['lead_comments'] + ', Agent name: ' + agent_name

            if friendly_name is not None and friendly_name is not '':
                ctx['lead_comments'] = ctx['lead_comments'] + ', Call source: ' + friendly_name
                ctx['receiver'] = friendly_name

            if phonenumber is not None and phonenumber is not '':
                ctx['lead_comments'] = ctx['lead_comments'] + ', Phonenumber: ' + phonenumber

            ctx['campaign_name'] = ''
            ctx['campaign_exists'] = False
            from buyercall.blueprints.contacts.models import Contact, Campaigns
            contact = Contact.query.filter(
                    Contact.id == lead.contact_id,
                    Contact.partnership_account_id == lead.partnership_account_id)\
                .first()
            if contact and contact.campaign_id:
                campaign = Campaigns.query.filter(
                    Campaigns.id == contact.campaign_id)\
                    .first()
                if campaign:
                    ctx['campaign_exists'] = True
                    ctx['campaign_name'] = campaign.display_name

            if adf_emails is not None and len(adf_emails) > 0:
                # Render text template for adf email
                adf_lead_email_template = _try_renderer_template('mail/adf_lead', ext='txt', **ctx)
                send_ses_email(recipients=adf_emails,
                               p_id=partner.id,
                               subject=partner.name + ' - ADF lead notification',
                               text=adf_lead_email_template
                               )

        if lead.status == 'missed' and storage.state == State.MISSED:
            # Render html template for email
            missed_lead_email_template = _try_renderer_template('mail/missed_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - Missed lead notification',
                           html=missed_lead_email_template
                           )
        elif lead.status == 'missed' and storage.state == State.CALLBACK:
            # Render html template for email
            missed_lead_email_template = _try_renderer_template('mail/missed_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - Missed lead notification',
                           html=missed_lead_email_template
                           )
        elif lead.status == 'retry-pending' or lead.status == 'missed' and storage.state == State.CALL_ME_BACK:
            # Render html template for email
            callback_lead_email_template = _try_renderer_template('mail/call_back', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - Missed Call-back lead notification',
                           html=callback_lead_email_template
                           )
        elif lead.status == 'completed' and storage.state == State.CAPTURED:
            # Render html template for email
            completed_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - Answered lead notification',
                           html=completed_lead_email_template
                           )
        else:
            # Render html template for email
            captured_lead_email_template = _try_renderer_template('mail/captured_lead', ext='html', **ctx)
            send_ses_email(recipients=emails,
                           p_id=partner.id,
                           subject=partner.name + ' - New lead notification',
                           html=captured_lead_email_template
                           )
    except Exception as e:
        app.logger.error(traceback.format_exc())


@celery.task
def send_callback_email(data, **kwargs):
    sub_id = data['Notification']['SubscriptionId']
    status = data['Notification']['Status']
    message = data['Notification']['Message']
    order_id = data['Notification']['OrderId']
    order_type = data['Notification']['OrderType']
    number = data['Notification']['CompletedTelephoneNumbers']['TelephoneNumber']
    ctx = {'sub_id': sub_id, 'status': status,
           'message': message, 'order_id': order_id,
           'order_type': order_type, 'number': number}

    from ..partnership.models import Partnership, PartnershipCpaasProviders, \
        PartnershipCpaasPhoneNumberSubscriptions as SubscriptionTable
    sub = SubscriptionTable.query.options(
        load_only("partnership_id")
    ).filter(SubscriptionTable.subscription_id == sub_id).first()
    partner = Partnership.query.filter(Partnership.id == sub.partnership_id).first()

    # Get the encryption key to decrypt email
    encrypt_key = celery.conf['CRYPTO_SECRET_KEY']
    # Retrieve the bandwidth cpaas details
    partner_cpaas = PartnershipCpaasProviders.partnership_bandwidth_credentials(sub.partnership_id)
    cipher = AESCipher(encrypt_key)
    # Decrypt the email
    decrypted_email = cipher.decrypt(partner_cpaas.cpaas_api_username)

    try:
        # Render html template for email
        subscription_email_template = _try_renderer_template('mail/subscription_webhook_email', ext='txt', **ctx)
        send_ses_email(recipients=[decrypted_email],
                       p_id=partner.id,
                       subject='Bandwidth Subscription Webhook Response',
                       text=subscription_email_template
                       )

    except Exception as e:
        log.error(traceback.format_exc())