Coder Social home page Coder Social logo

python-sample-send-mail's Introduction

[ARCHIVED] Sending mail via Microsoft Graph from Python

IMPORTANT

This project is being archived and replaced with the Build Python Django apps with Microsoft Graph. As part of the archival process, we're closing all open issues and pull requests.

You can continue to use this sample "as-is", but it won't be maintained moving forward. We apologize for any inconvenience.

Microsoft Graph provides REST APIs for working with Outlook mail data that give your app the ability to perform user-centric actions such as sending and receiving email. This sample provides an example of how to send email from a Python web application, using the Requests HTTP library to work with these Microsoft Graph APIs:

API Endpoint
Get user profile /me docs
Get profile photo /me/photo docs
Upload to OneDrive /me/drive/root/children/{filename}/content docs
Create sharing link /me/drive/items/{itemId}/createLink docs
Send mail /me/microsoft.graph.sendMail docs

For additional information about this sample, see Get started with Microsoft Graph in a Python app.

This sample is a web app, but if you're looking for an example of how to work with Microsoft Graph from Python console apps (also known as terminal applications or command line tools), see Python console application for Microsoft Graph.

Installation

To install and configure the samples, see the instructions in Installing the Python REST samples. Note that this sample requires the following permissions: User.Read, Mail.Send, and Files.ReadWrite.

After you've completed those steps, you'll be able to run the sample.py sample as covered below.

Running the sample

  1. At the command prompt: python sample.py
  2. In your browser, navigate to http://localhost:5000
  3. Choose Connect and authenticate with a Microsoft identity (work or school account or Microsoft account).

You'll then see a form that can be used to send email on behalf of the authenticated user:

Send mail form

By default, the email will be sent to the address of the authenticated user, but you can edit the To, Subject, and Body fields if desired. For multiple recipients on the To line, separate the email addresses with semicolons. Note that the Body field allows the use of HTML for formatting.

After you've made any edits to the email, choose Send Message. The email will then be sent via Microsoft Graph, and if all goes well an HTTP status code of 202 will be returned:

Mail sent

The 202 status code is defined in the HTTP specification as follows:

The request has been accepted for processing, but the processing has not been completed. The request might or might not eventually be acted upon, as it might be disallowed when processing actually takes place.

In other words, Graph has received and accepted our request to send an email. If the request was invalid, however, Graph may not accept it and then you'll get a different response. For example, here's what happens if you try to send to an invalid email address:

Mail sent - error

The 400 status code means "The request could not be understood by the server due to malformed syntax." Note that a JSON error message is also returned, which describes the problem:

{'error': {'code': 'ErrorInvalidRecipients',
           'innerError': {'date': '2017-12-01T02:58:24',
                          'request-id': 'd0addb2f-8366-47f7-8f3e-fa8616cd279f'},
           'message': "At least one recipient isn't valid., Recipient "
                      '"BAD_ADDRESS" isn\'t resolved. All recipients must be '
                      'resolved before a message can be submitted.'}}

This sample uses delegated permissions to send mail on behalf of the currently authenticated user, identified as 'me' in Graph API calls. You can also send mail on behalf of other users, if you have administrator consent for the appropriate application permissions. See the Microsoft Graph permissions reference for more information about Graph's permission model.

Sendmail helper function

The sendmail() function in sample.py is a helper to make it easy to send email from Python applications and services via Microsoft Graph. For example, here's the line of code that sends mail in sample.py, passing the values from the send mail form to the helper function:

response = sendmail(client=MSGRAPH,
                    subject=flask.request.args['subject'],
                    recipients=flask.request.args['email'].split(';'),
                    body=flask.request.args['body'],
                    attachments=[profile_pic])

The helper function creates dictionaries for the recipients and any attachments, then assembles those into the JSON data that is sent to Graph. Here's the complete source code for sendmail():

def sendmail(*, client, subject=None, recipients=None, body='',
             content_type='HTML', attachments=None):
    """Helper to send email from current user.

    client       = user-authenticated flask-oauthlib client instance
    subject      = email subject (required)
    recipients   = list of recipient email addresses (required)
    body         = body of the message
    content_type = content type (default is 'HTML')
    attachments  = list of file attachments (local filenames)

    Returns the response from the POST to the sendmail API.
    """

    # Verify that required arguments have been passed.
    if not all([client, subject, recipients]):
        raise ValueError('sendmail(): required arguments missing')

    # Create recipient list in required format.
    recipient_list = [{'EmailAddress': {'Address': address}}
                      for address in recipients]

    # Create list of attachments in required format.
    attached_files = []
    if attachments:
        for filename in attachments:
            b64_content = base64.b64encode(open(filename, 'rb').read())
            mime_type = mimetypes.guess_type(filename)[0]
            mime_type = mime_type if mime_type else ''
            attached_files.append( \
                {'@odata.type': '#microsoft.graph.fileAttachment',
                 'ContentBytes': b64_content.decode('utf-8'),
                 'ContentType': mime_type,
                 'Name': filename})

    # Create email message in required format.
    email_msg = {'Message': {'Subject': subject,
                             'Body': {'ContentType': content_type, 'Content': body},
                             'ToRecipients': recipient_list,
                             'Attachments': attached_files},
                 'SaveToSentItems': 'true'}

    # Do a POST to Graph's sendMail API and return the response.
    return client.post('me/microsoft.graph.sendMail',
                       headers=request_headers(),
                       data=email_msg,
                       format='json')

Contributing

These samples are open source, released under the MIT License. Issues (including feature requests and/or questions about this sample) and pull requests are welcome. If there's another Python sample you'd like to see for Microsoft Graph, we're interested in that feedback as well โ€” please log an issue and let us know!

This project has adopted the Microsoft Open Source Code of Conduct. For more information, see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Resources

Documentation:

Samples:

Packages:

python-sample-send-mail's People

Contributors

jasonjoh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-sample-send-mail's Issues

builtins.KeyError

Getting this error right after signing in. Same issue on chrome & IE 11. Tried incognito & private mode. Any ideas?

Traceback (most recent call last):
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2309, in call
return self.wsgi_app(environ, start_response)
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2295, in wsgi_app
response = self.handle_exception(e)
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask_compat.py", line 35, in reraise
raise value
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask_compat.py", line 35, in reraise
raise value
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1799, in dispatch_request
return self.view_functionsrule.endpoint
File "C:\repos\python-sample-send-mail-master\sample.py", line 44, in authorized
if str(flask.session['state']) != str(flask.request.args['state']):
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\werkzeug\local.py", line 377, in
getitem = lambda x, i: x._get_current_object()[i]
File "C:\Users\dwilkie\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\sessions.py", line 83, in getitem
return super(SecureCookieSession, self).getitem(key)
KeyError: 'state'

Regarding listing calendar events

Hi,

I am trying to hit this api me/calendars in the same code base. It's not working.

I am getting this following response.

{
error: {
code: "ErrorAccessDenied",
message: "Access is denied. Check credentials and try again.",
innerError: {
request-id: "7ba3122c-5c9f-42f9-b65e-ba01724bca67",
date: "2018-02-14T00:30:48"
}
}
}

even though I am sending access token in the header.

When I hit this me/microsoft.graph.sendMail endpoint, it's working.

Please let me know what I am missing.

Thanks,
Iniyan P

attachments aren't working

Attachments silently fail, although the JSON data appears to be identical to the Quick Start samples that do work. Investigate and resolve, then add attachment option to the sample.

Webhooks

Hi guys,
I'm trying to use this sample to write a similar program which uses webhooks on heroku.
here's my code:

import uuid
import redis
from flask import *
import os
import json
from flask_oauthlib.client import OAuth
import requests

redis_url = os.environ['REDISTOGO_URL']
redis_client = redis.from_url(redis_url)
 
CLIENT_ID = os.environ['CLIENT_ID']
CLIENT_SECRET = os.environ['CLIENT_SECRET']
REDIRECT_URI = URL
RESOURCE = 'https://graph.microsoft.com/'
API_VERSION = 'v1.0'
SCOPES = ['User.Read','Files.Read.All', 'Files.ReadWrite.All'] 
AUTHORITY_URL = 'https://login.microsoftonline.com/common'
AUTH_ENDPOINT = '/oauth2/v2.0/authorize'
TOKEN_ENDPOINT = '/oauth2/v2.0/token'




app = Flask(__name__, template_folder='static/templates')
app.debug = True
 

app.secret_key = os.environ['FLASK_SECRET_KEY']


OAUTH = OAuth(app)
MSGRAPH = OAUTH.remote_app(
    'microsoft', consumer_key=CLIENT_ID, consumer_secret=CLIENT_SECRET,
    request_token_params={'scope': SCOPES},
    base_url=RESOURCE + API_VERSION + '/',
    request_token_url=None, access_token_method='POST',
    access_token_url=AUTHORITY_URL + TOKEN_ENDPOINT,
    authorize_url=AUTHORITY_URL + AUTH_ENDPOINT)

@app.route('/')
@app.route('/welcome')
def homepage():
    """Render the home page."""
    return render_template('homepage.html', sample='Flask-OAuthlib')

@app.route('/login')
def login():
    """Prompt user to authenticate."""
    session['state'] = str(uuid.uuid4())
    return MSGRAPH.authorize(callback=REDIRECT_URI, state=session['state'])


@app.route('/login/authorized')
def authorized():
    """Handler for the application's Redirect Uri."""

    if str(session['state']) != str(request.args['state']):
        raise Exception('state returned to redirect URL does not match!')
    response = MSGRAPH.authorized_response()
    session['access_token'] = response['access_token']
    
    endpoint = 'me'
    headers = {'SdkVersion': 'sample-python-flask',
               'x-client-SKU': 'sample-python-flask',
               'client-request-id': session.get('state'),
               'return-client-request-id': 'true'
               }
    graphdata = MSGRAPH.get(endpoint, headers=headers).data
    redis_client.hset('tokens', graphdata["id"], response['access_token'])
    
    endpoint = 'subscriptions'
    data = """{"changeType": "updated",
            "notificationUrl": "https://onedrive-votiro.herokuapp.com/webhook",
            "resource": "/me/drive/root",
            "expirationDateTime": "2018-02-02T11:23:00.000Z",
            "clientState": "STATE" 
            }""" 
            
    subscription = MSGRAPH.post(endpoint, headers=headers, content_type='application/json', data = data).data
    redis_client.hset('tokens', subscription["id"], response['access_token'])
    
    return redirect('/graphcall')


def getDelta(id):

    print 'in delta'
    location = "me/drive/root/delta"
    '''
    headers = {'SdkVersion': 'sample-python-flask',
           'x-client-SKU': 'sample-python-flask',
           'client-request-id': str(uuid.uuid4()),
           'return-client-request-id': 'true'
           }
    '''
    token = redis_client.hget('tokens', id)
    return json.loads(MSGRAPH.get(location, token=token).data)

@app.route('/webhook', methods=['POST'])
def webhook():
    '''Respond to the webhook challenge (POST request) by echoing back the challenge parameter.'''
    if request.args.has_key('validationToken'):
        rv = (request.args.get('validationToken'), 200, {'Content-Type':'text/plain'})
        resp = make_response(rv)
        return resp
    else:
        data = json.loads(request.data)["value"]
        print data
        for item in data:
            clientState = item["clientState"]
            if clientState == "STATE":
                id = item["subscriptionId"]
                response = getDelta(id)
            else:
                pass
                #false notification, do nothing
            return status.HTTP_201_CREATED
            
@app.route('/graphcall')
def graphcall():
    """Confirm user authentication by calling Graph and displaying some data."""
    return render_template('graphcall.html') #redirect to onedrive

@MSGRAPH.tokengetter
def get_token():
    """Called by flask_oauthlib.client to retrieve current access token."""
    return (session.get('access_token'), '')
    

if __name__ == '__main__':
    app.run()

The main issue, one whice i cannot find any reference online, is when Posting a subscription request (for the webhook), I'm getting the following error:

{u'error': {u'code': u'ExtensionError', u'message': u'Operation: Create; Exception: [Status Code: Forbidden; Reason: Forbidden]', u'innerError': {u'request-id': u'79709ce8-b29d-41ed-9452-607ecab7e48d', u'date': u'2018-01-03T09:03:41'}}}

This tells me nothing basically and I'm currently stuck on this..
Any ideas what am I doing wrong?

OSError: [WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions

When I run the sample application in a PowerShell (run as Administrator), I received the following error:

(meetingStats) PS C:\Documents\code\meetingStats> python .\sample.py
Traceback (most recent call last):
  File ".\sample.py", line 260, in <module>
    APP.run()
  File "C:\Documents\code\meetingStats\lib\site-packages\flask\app.py", line 841, in run
    run_simple(host, port, self, **options)
  File "C:\Documents\code\meetingStats\lib\site-packages\werkzeug\serving.py", line 795, in run_simple
    s.bind(get_sockaddr(hostname, port, address_family))
OSError: [WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions

This might be caused by some other program using the default Flask app port of 5000. When I change the the final line at

to:

APP.run(port=9999)

but then of course, after successful authentication, it (via Flask) tries to redirect to the default port of 5000, and when I change 5000 to 9999 in the URL in my browser and hit enter, it works correctly, shows me the email sending screen, etc.

Should I update README? But this would be half-baked solution, do you know how to tell Flask to not to redirect to 5000 again? Because obviously APP.run(port=9999) leads to problems again, which can be very disappointing for people who are just getting started.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.