Skip to content

Instantly share code, notes, and snippets.

@dadevel
Last active February 28, 2023 13:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dadevel/a821bbfcafd25f951df971d413e69045 to your computer and use it in GitHub Desktop.
Save dadevel/a821bbfcafd25f951df971d413e69045 to your computer and use it in GitHub Desktop.
Send Email with Azure/M365
#!/usr/bin/env python3
from argparse import ArgumentParser
import json
import sys
import requests
# Retrieve an access token via the device code flow:
# roadtx auth --tokenfile ~/.cache/azmail.json --resource https://outlook.office.com --client d3590ed6-52b3-4102-aeff-aad2292ab01c --tenant contoso.com --device-code
# Refresh an existing access token:
# roadtx auth --tokenfile ~/.cache/azmail.json --resource https://outlook.office.com --client d3590ed6-52b3-4102-aeff-aad2292ab01c --tenant contoso.com --refresh-token "$(jq -r .refreshToken ~/.cache/azmail.json)"
# Send an email:
# azmail -t ~/.cache/aztoken.json -s 'Test 1' -b 'Hello world!' -r john.doe@contoso.com
# docs:
# - https://learn.microsoft.com/en-us/graph/api/user-sendmail
# - https://learn.microsoft.com/en-us/previous-versions/office/office-365-api/api/version-2.0/mail-rest-operations#SendMessages
# I could not find a public client with the Mail.Send permission on graph.microsoft.com.
# Therefore I had to fall back to the deprecated outlook.office.com API for now.
# See e.g. https://github.com/secureworks/family-of-client-ids-research/blob/main/scope-map.txt
#ENDPOINT = 'https://graph.microsoft.com/v1.0/me/sendMail'
ENDPOINT = 'https://outlook.office.com/api/v2.0/me/sendmail'
def main() -> None:
entrypoint = ArgumentParser()
entrypoint.add_argument('-t', '--token', required=True)
entrypoint.add_argument('-s', '--subject', required=True)
entrypoint.add_argument('-b', '--body', required=True)
entrypoint.add_argument('--content-type', choices=('text', 'html'), default='text')
entrypoint.add_argument('-r', '--recipient', dest='recipients', action='append', required=True)
opts = entrypoint.parse_args()
if opts.token.startswith('eY'):
token = opts.token
else:
with open(opts.token) as file:
data = json.load(file)
token = data['accessToken']
response = requests.post(
ENDPOINT,
headers={'Authorization': f'Bearer {token}'},
json=dict(
Message=dict(
Subject=opts.subject,
Body=dict(
ContentType=opts.content_type,
Content=opts.body,
),
ToRecipients=[dict(emailAddress=dict(address=address)) for address in opts.recipients],
),
SaveToSentItems=True,
),
)
if response.status_code != 202:
data = response.json()
print(data['error']['message'], file=sys.stderr)
exit(1)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment