Sends an email from the inputted email address to the
    list of given recipients with the inputted subject and
    body.  This will also attach the inputted list of
    attachments to the email.  The server value will default 
    to mail.<sender_domain> and you can use a ':' to specify 
    a port for the server.
    
    :param      sender          <str>
    :param      recipients      <list> [ <str>, .. ]
    :param      subject         <str>
    :param      body            <str>
    :param      attachments     <list> [ <str>, .. ]
    :param      cc              <list> [ <str>, .. ]
    :param      bcc             <list> [ <str>, .. ]
    :param      contentType     <str>
    :param      server          <str>
    
    :return     <bool> success

def sendEmail(sender,
              recipients,
              subject,
              body,
              attachments=None,
              cc=None,
              bcc=None,
              contentType='text/html',
              server=None,
              useMSExchange=None,
              encoding='utf-8',
              raiseErrors=False):
    """
    Sends an email from the inputted email address to the
    list of given recipients with the inputted subject and
    body.  This will also attach the inputted list of
    attachments to the email.  The server value will default 
    to mail.<sender_domain> and you can use a ':' to specify 
    a port for the server.
    
    :param      sender          <str>
    :param      recipients      <list> [ <str>, .. ]
    :param      subject         <str>
    :param      body            <str>
    :param      attachments     <list> [ <str>, .. ]
    :param      cc              <list> [ <str>, .. ]
    :param      bcc             <list> [ <str>, .. ]
    :param      contentType     <str>
    :param      server          <str>
    
    :return     <bool> success
    """
    if attachments is None:
        attachments = []
    if cc is None:
        cc = []
    if bcc is None:
        bcc = []

    if server is None:
        server = NOTIFY_SERVER
    if useMSExchange is None:
        useMSExchange = NOTIFY_SERVER_MSX

    # normalize the data
    sender = nstr(sender)
    recipients = map(nstr, recipients)

    # make sure we have valid information
    if not isEmail(sender):
        err = errors.NotifyError('%s is not a valid email address' % sender)
        logger.error(err)
        return False

    # make sure there are recipients
    if not recipients:
        err = errors.NotifyError('No recipients were supplied.')
        logger.error(err)
        return False

    # build the server domain
    if not server:
        err = errors.NotifyError('No email server specified')
        logger.error(err)
        return False

    # create the email
    msg = MIMEMultipart(_subtype='related')
    msg['Subject'] = projex.text.toUtf8(subject)
    msg['From'] = sender
    msg['To'] = ','.join(recipients)
    msg['Cc'] = ','.join([nstr(addr) for addr in cc if isEmail(addr)])
    msg['Bcc'] = ','.join([nstr(addr) for addr in bcc if isEmail(addr)])
    msg['Date'] = nstr(datetime.datetime.now())
    msg['Content-type'] = 'Multipart/mixed'

    msg.preamble = 'This is a multi-part message in MIME format.'
    msg.epilogue = ''

    # build the body
    bodyhtml = projex.text.toUtf8(body)
    eattach = []

    # include inline images
    filepaths = re.findall('<img\s+src="(file:///[^"]+)"[^/>]*/?>', bodyhtml)
    for filepath in filepaths:
        filename = filepath.replace('file:///', '')
        if os.path.exists(filename) and filename not in attachments:
            # replace with the attachment id
            cid = 'cid:%s' % os.path.basename(filename)
            bodyhtml = bodyhtml.replace(filename, cid)

            # add the image to the attachments
            fp = open(nstr(filename), 'rb')
            msgImage = MIMEImage(fp.read())
            fp.close()

            # add the msg image to the msg
            content_id = '<%s>' % os.path.basename(filename)
            inline_link = 'inline; filename="%s"' % os.path.basename(filename)
            msgImage.add_header('Content-ID', content_id)
            msgImage.add_header('Content-Disposition', inline_link)

            eattach.append(msgImage)
            attachments.append(filename)

    # create the body text
    msgText = MIMEText(bodyhtml, contentType, encoding)
    msgText['Content-type'] = contentType

    # include attachments
    for attach in attachments:
        fp = open(nstr(attach), 'rb')
        txt = MIMEBase('application', 'octet-stream')
        txt.set_payload(fp.read())
        fp.close()

        encode_base64(txt)
        attachment = 'attachment; filename="%s"' % os.path.basename(attach)
        txt.add_header('Content-Disposition', attachment)
        eattach.append(txt)

    eattach.insert(0, msgText)

    # add the attachments to the message
    for attach in eattach:
        msg.attach(attach)

    # create the connection to the email server
    try:
        smtp_server = smtplib.SMTP(nstr(server))
    except socket.gaierror, err:
        logger.error(err)
        if raiseErrors:
            raise
        return False
    except Exception, err:
        logger.error(err)
        if raiseErrors:
            raise
        return False

    # connect to a microsoft exchange server if specified
    if useMSExchange:
        success, response = connectMSExchange(smtp_server)
        if not success:
            logger.debug('Could not connect to MS Exchange: ' + response)

    try:
        smtp_server.sendmail(sender, recipients, msg.as_string())
        smtp_server.close()
    except Exception, err:
        logger.error(err)
        if raiseErrors:
            raise
        return False

    return True