示例#1
0
def main():
    t = Terminal()
    payload_str = get_env_var('EVENT_PAYLOAD')
    print(f'{t.cyan}Starting the slack notifier{t.normal}')

    payload = json.loads(payload_str)
    pr_number = payload.get('pull_request', {}).get('number')

    # Get the PR information in order to get information like metadata
    org_name = 'demisto'
    repo_name = 'content'
    gh = Github(get_env_var('CONTENTBOT_GH_ADMIN_TOKEN'), verify=False)
    content_repo = gh.get_repo(f'{org_name}/{repo_name}')
    pr = content_repo.get_pull(pr_number)
    metadata_files = [file for file in pr.get_files() if file.filename.endswith('_metadata.json')]

    # Build all blocks of the message
    header = create_pr_title(pr)
    pull_request_segment = create_pull_request_segment(pr)
    packs_segment = create_packs_segment(metadata_files)
    blocks = header + pull_request_segment + packs_segment
    print(f'{t.yellow}Finished preparing message: \n{pformat(blocks)}{t.normal}')

    # Send message
    slack_token = get_env_var('CORTEX_XSOAR_SLACK_TOKEN')
    client = WebClient(token=slack_token)
    slack_post_message(client, blocks, pr)
    print(f'{t.cyan}Slack message sent successfully{t.normal}')
示例#2
0
    def test_no_env_var(self):
        """
        Scenario: Try getting an environment variable

        Given
        - Using the 'get_env_var' function

        When
        - The environment variable does not exist
        - No 'default_val' argument was passed when the function was called

        Then
        - Ensure a 'EnvVariableError' exception is raised
        """
        with pytest.raises(EnvVariableError):
            get_env_var('MADE_UP_ENV_VARIABLE')
示例#3
0
    def test_empty_env_var(self, monkeypatch):
        """
        Scenario: Try getting an environment variable

        Given
        - Using the 'get_env_var' function

        When
        - The environment variable's value is an empty string
        - No 'default_val' argument was passed when the function was called

        Then
        - Ensure a 'EnvVariableError' exception is raised
        """
        monkeypatch.setenv('MADE_UP_ENV_VARIABLE', '')
        with pytest.raises(EnvVariableError):
            get_env_var('MADE_UP_ENV_VARIABLE')
示例#4
0
    def test_existing_env_var(self, monkeypatch):
        """
        Scenario: Try getting an environment variable

        Given
        - Using the 'get_env_var' function

        When
        - The environment variable's value is 'LEROY JENKINS'
        - No 'default_val' argument was passed when the function was called

        Then
        - Ensure 'LEROY JENKINS' is returned from the function
        """
        monkeypatch.setenv('MADE_UP_ENV_VARIABLE', 'LEROY JENKINS')
        env_var_val = get_env_var('MADE_UP_ENV_VARIABLE')
        assert env_var_val == 'LEROY JENKINS'
示例#5
0
    def test_no_env_var_with_default(self):
        """
        Scenario: Try getting an environment variable

        Given
        - Using the 'get_env_var' function

        When
        - The environment variable does not exist
        - The 'default_val' argument was passed with a value of 'TIMOTHY'

        Then
        - Ensure 'TIMOTHY' is returned from the function
        """
        default_val = 'TIMOTHY'
        env_var_val = get_env_var('MADE_UP_ENV_VARIABLE', default_val)
        assert env_var_val == default_val
示例#6
0
    def test_existing_env_var_with_default(self, monkeypatch):
        """
        Scenario: Try getting an environment variable

        Given
        - Using the 'get_env_var' function

        When
        - The environment variable's value is 'LEROY JENKINS'
        - The 'default_val' argument was passed with a value of 'TIMOTHY'

        Then
        - Ensure 'LEROY JENKINS' is returned from the function
        """
        monkeypatch.setenv('MADE_UP_ENV_VARIABLE', 'LEROY JENKINS')
        default_val = 'TIMOTHY'
        env_var_val = get_env_var('MADE_UP_ENV_VARIABLE', default_val)
        assert env_var_val == 'LEROY JENKINS'
示例#7
0
def test_env_vars():
    """test if the critical env variables are available in the environment"""

    CELERY_BROKER_URL = get_env_var('CELERY_BROKER_URL')

    DB_URL = get_env_var('SQLALCHEMY_DATABASE_URI')
    DB_URL_TEST = get_env_var('SQLALCHEMY_DATABASE_URI_TEST')

    SECRET_KEY = get_env_var('MPORTER_SECRET')

    MAILGUN_KEY = get_env_var('MAILGUN_KEY')
    MAILGUN_SANDBOX = get_env_var('MAILGUN_SANDBOX')

    assert CELERY_BROKER_URL is not None
    assert DB_URL is not None
    assert DB_URL_TEST is not None
    assert SECRET_KEY is not None
    assert MAILGUN_KEY is not None
    assert MAILGUN_SANDBOX is not None
def update_handler(event, context):
    """
    Save Original Email and Update Email Content Using WorkMail Lambda Integration

    Parameters
    ----------
    email_summary: dict, required
        Amazon WorkMail Message Summary Input Format
        For more information, see https://docs.aws.amazon.com/workmail/latest/adminguide/lambda.html

        {
            "summaryVersion": "2019-07-28",                         # AWS WorkMail Message Summary Version
            "envelope": {
                "mailFrom" : {
                    "address" : "*****@*****.**"                  # String containing from email address
                },
                "recipients" : [                                    # List of all recipient email addresses
                   { "address" : "*****@*****.**" },
                   { "address" : "*****@*****.**" }
                ]
            },
            "sender" : {
                "address" :  "*****@*****.**"                   # String containing sender email address
            },
            "subject" : "Hello From Amazon WorkMail!",              # String containing email subject (Truncated to first 256 chars)"
            "messageId": "00000000-0000-0000-0000-000000000000",    # String containing message id for retrieval using workmail flow API
            "invocationId": "00000000000000000000000000000000",     # String containing the id of this lambda invocation. Useful for detecting retries and avoiding duplication
            "flowDirection": "INBOUND",                             # String indicating direction of email flow. Value is either "INBOUND" or "OUTBOUND"
            "truncated": false                                      # boolean indicating if any field in message was truncated due to size limitations
        }

    context: object, required
    Lambda Context runtime methods and attributes. See https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html

    Returns
    -------
    Amazon WorkMail Sync Lambda Response Format
    For more information, see https://docs.aws.amazon.com/workmail/latest/adminguide/lambda.html#synchronous-schema
        return {
          'actions': [                                              # Required, should contain at least 1 list element
          {
            'action' : {                                            # Required
              'type': 'string',                                     # Required. Can be "BOUNCE", "DROP" or "DEFAULT"
              'parameters': { <various> }                           # Optional. For bounce, <various> can be {"bounceMessage": "message that goes in bounce mail"}
            },
            'recipients': list of strings,                          # Optional. Indicates list of recipients for which this action applies
            'default': boolean                                      # Optional. Indicates whether this action applies to all recipients
          }
        ]}

    """

    logger.info(f"Received event: {event}")
    email_from = event['envelope']['mailFrom']
    recipients = event['envelope']['recipients']
    message_id = event['messageId']
    key = str(uuid.uuid4())

    # Determine if the message is internal
    if utils.extract_domains([email_from
                              ]) == utils.extract_domains(recipients):
        internal_message = True
    else:
        internal_message = False

    update_internal_msg = (
        utils.get_env_var('UPDATE_INTERNAL_MESSAGES') == 'True')
    update_external_msg = (
        utils.get_env_var('UPDATE_EXTERNAL_MESSAGES') == 'True')
    save_and_update_msg = False

    if internal_message:
        if update_internal_msg:
            save_and_update_msg = True
    else:
        if update_external_msg:
            save_and_update_msg = True

    try:
        # 1. Download email
        downloaded_email = utils.download_email(message_id)
        # 2. Save and update original email
        if save_and_update_msg:
            saved_bucket = utils.get_env_var('SAVED_EMAIL_BUCKET')
            updated_bucket = utils.get_env_var('UPDATED_EMAIL_BUCKET')
            # 3. Save the orginal, unmodified, email message source
            utils.save_email(saved_bucket, downloaded_email.as_bytes(),
                             key + ".eml")
            # 4. Save the event data (metadata) about the message so we know the envelope details that aren't in the message source
            utils.save_email(saved_bucket, json.dumps(event), key + ".json")
            # 5. Update the email with the desired modifications
            updated_email = utils.update_email(downloaded_email,
                                               event['subject'],
                                               event['flowDirection'], key)
            logger.info("Providing modified message for WorkMail")
            utils.update_workmail(message_id, updated_bucket, updated_email,
                                  key)
        else:
            logger.info("Preserving original message for WorkMail")

    except ClientError as e:
        if e.response['Error']['Code'] == 'MessageFrozen':
            # Redirect emails are not eligible for update, handle it gracefully.
            logger.info(
                f"Message {message_id} is not eligible for update. This is usually the case for a redirected email"
            )
        else:
            logger.error(e.response['Error']['Message'])
            if e.response['Error']['Code'] == 'ResourceNotFoundException':
                logger.error(
                    f"Message {message_id} does not exist. Messages in transit are no longer accessible after 1 day"
                )
            elif e.response['Error']['Code'] == 'InvalidContentLocation':
                logger.error(
                    'WorkMail could not access the updated email content. See https://docs.aws.amazon.com/workmail/latest/adminguide/update-with-lambda.html'
                )
            raise (e)

    # Resume normal email flow
    return {
        'actions': [{
            'action': {
                'type': 'DEFAULT'
            },
            'allRecipients': 'true'
        }]
    }
示例#9
0
def main():
    """Handles External PRs (PRs from forks)

    Performs the following operations:
    1. If the external PR's base branch is master we create a new branch and set it as the base branch of the PR.
    2. Labels the PR with the "Contribution" label. (Adds the "Hackathon" label where applicable.)
    3. Assigns a Reviewer.
    4. Creates a welcome comment

    Will use the following env vars:
    - CONTENTBOT_GH_ADMIN_TOKEN: token to use to update the PR
    - EVENT_PAYLOAD: json data from the pull_request event
    """
    t = Terminal()
    payload_str = get_env_var('EVENT_PAYLOAD')
    if not payload_str:
        raise ValueError('EVENT_PAYLOAD env variable not set or empty')
    payload = json.loads(payload_str)
    print(f'{t.cyan}Processing PR started{t.normal}')

    org_name = 'demisto'
    repo_name = 'content'
    gh = Github(get_env_var('CONTENTBOT_GH_ADMIN_TOKEN'), verify=False)
    content_repo = gh.get_repo(f'{org_name}/{repo_name}')
    pr_number = payload.get('pull_request', {}).get('number')
    pr = content_repo.get_pull(pr_number)

    # Add 'Contribution' Label to PR
    contribution_label = 'Contribution'
    pr.add_to_labels(contribution_label)
    print(f'{t.cyan}Added "Contribution" label to the PR{t.normal}')

    # check base branch is master
    if pr.base.ref == 'master':
        print(f'{t.cyan}Determining name for new base branch{t.normal}')
        branch_prefix = 'contrib/'
        new_branch_name = f'{branch_prefix}{pr.head.label.replace(":", "_")}'
        existant_branches = content_repo.get_git_matching_refs(
            f'heads/{branch_prefix}')
        potential_conflicting_branch_names = [
            branch.ref.lstrip('refs/heads/') for branch in existant_branches
        ]
        # make sure new branch name does not conflict with existing branch name
        while new_branch_name in potential_conflicting_branch_names:
            # append or increment digit
            if not new_branch_name[-1].isdigit():
                new_branch_name += '-1'
            else:
                digit = str(int(new_branch_name[-1]) + 1)
                new_branch_name = f'{new_branch_name[:-1]}{digit}'
        master_branch_commit_sha = content_repo.get_branch('master').commit.sha
        # create new branch
        print(f'{t.cyan}Creating new branch "{new_branch_name}"{t.normal}')
        content_repo.create_git_ref(f'refs/heads/{new_branch_name}',
                                    master_branch_commit_sha)
        # update base branch of the PR
        pr.edit(base=new_branch_name)
        print(
            f'{t.cyan}Updated base branch of PR "{pr_number}" to "{new_branch_name}"{t.normal}'
        )

    # assign reviewers / request review from
    reviewer_to_assign = determine_reviewer(REVIEWERS, content_repo)
    pr.add_to_assignees(reviewer_to_assign)
    pr.create_review_request(reviewers=[reviewer_to_assign])
    print(f'{t.cyan}Assigned user "{reviewer_to_assign}" to the PR{t.normal}')
    print(
        f'{t.cyan}Requested review from user "{reviewer_to_assign}"{t.normal}')

    # create welcome comment
    body = WELCOME_MSG.format(selected_reviewer=reviewer_to_assign)
    pr.create_issue_comment(body)
    print(f'{t.cyan}Created welcome comment{t.normal}')
    # 'AR',
    # 'PH',
    # 'CO',
    # 'MY',
    # 'VE',
    # 'TH',
    # 'PK',
    # ]

    country_code = args.country_code
    print('Country:', country_code)



    # Choose Number of Nodes To Distribute Credentials: e.g. jobarray=0-4, cpu_per_task=20, credentials = 90 (<100)
    SLURM_JOB_ID            = get_env_var('SLURM_JOB_ID',0)
    SLURM_ARRAY_TASK_ID     = get_env_var('SLURM_ARRAY_TASK_ID',0)
    SLURM_ARRAY_TASK_COUNT  = get_env_var('SLURM_ARRAY_TASK_COUNT',1)
    SLURM_JOB_CPUS_PER_NODE = get_env_var('SLURM_JOB_CPUS_PER_NODE',mp.cpu_count())

    # +
    if 'samuel' in socket.gethostname().lower():
        path_to_data='../../data'
    else:
        path_to_data='/scratch/spf248/twitter/data'

    path_to_keys = os.path.join(path_to_data,'keys','twitter')
    path_to_users = os.path.join(path_to_data,'users')
    path_to_locations = os.path.join(path_to_data,'locations','profiles')
    path_to_friends = os.path.join(path_to_data,'friends','API',country_code)
    os.makedirs(path_to_friends, exist_ok=True)
示例#11
0
from utils import get_env_var

SEND_MAIL_HOUR = 8
CELERY_BROKER_URL = get_env_var('RABBITMQ_BIGWIG_URL')
DB_URL = get_env_var('DATABASE_URL')
DB_URL_TEST = get_env_var('HEROKU_POSTGRESQL_GRAY_URL')

SECRET_KEY = get_env_var('MPORTER_SECRET')

MAILGUN_KEY = get_env_var('MAILGUN_API_KEY')
MAILGUN_SANDBOX = get_env_var('MAILGUN_DOMAIN')
MAILGUN_URL = 'https://api.mailgun.net/v2/{0}/messages'.format(MAILGUN_SANDBOX)
MAILGUN_TESTMAIL_ADDR = 'postmaster@{}'.format(MAILGUN_SANDBOX)
示例#12
0
def main():
    """Creates Internal PRs from Merged External PRs

    Performs the following operations:
    1. Creates new PR.
        A) Uses body of merged external PR as the body of the new PR.
        B) Uses base branch of merged external PR as head branch of the new PR to master.
        C) Adds 'docs-approved' label if it was on the merged external PR.
        D) Requests review from the same users as on the merged external PR.
        E) Assigns the same users as on the merged external PR.

    Will use the following env vars:
    - CONTENTBOT_GH_ADMIN_TOKEN: token to use to update the PR
    - EVENT_PAYLOAD: json data from the pull_request event
    """
    t = Terminal()
    payload_str = get_env_var('EVENT_PAYLOAD')
    if not payload_str:
        raise ValueError('EVENT_PAYLOAD env variable not set or empty')
    payload = json.loads(payload_str)
    print(f'{t.cyan}Creation of Internal PR started{t.normal}')

    org_name = 'demisto'
    repo_name = 'content'
    gh = Github(get_env_var('CONTENTBOT_GH_ADMIN_TOKEN'), verify=False)
    content_repo = gh.get_repo(f'{org_name}/{repo_name}')
    pr_number = payload.get('pull_request', {}).get('number')
    merged_pr = content_repo.get_pull(pr_number)
    merged_pr_url = merged_pr.html_url
    body = f'## Original External PR\r\n[external pull request]({merged_pr_url})\r\n\r\n'
    title = merged_pr.title
    body += merged_pr.body
    base_branch = 'master'
    head_branch = merged_pr.base.ref
    pr = content_repo.create_pull(title=title,
                                  body=body,
                                  base=base_branch,
                                  head=head_branch,
                                  draft=False)
    print(f'{t.cyan}Internal PR Created - {pr.html_url}{t.normal}')

    labels = [label.name for label in merged_pr.labels]
    docs_approved_label = 'docs-approved'
    if docs_approved_label in labels:
        pr.add_to_labels(docs_approved_label)
        print(f'{t.cyan}"docs-approved" label added{t.normal}')

    merged_by = merged_pr.merged_by.login
    reviewers, _ = merged_pr.get_review_requests()
    reviewers_logins = [reviewer.login for reviewer in reviewers]
    # request reviews from the same people as in the merged PR
    new_pr_reviewers = [merged_by] if merged_by else reviewers_logins
    pr.create_review_request(reviewers=new_pr_reviewers)
    print(f'{t.cyan}Requested review from {new_pr_reviewers}{t.normal}')

    # assign same users as in the merged PR
    assignees = [assignee.login for assignee in merged_pr.assignees]
    pr.add_to_assignees(*assignees)
    print(f'{t.cyan}Assigned users {assignees}{t.normal}')

    # remove branch protections
    print(f'{t.cyan}Removing protection from branch "{head_branch}"{t.normal}')
    contrib_branch = content_repo.get_branch(head_branch)
    contrib_branch.remove_protection()
    contrib_branch.remove_required_status_checks()
    contrib_branch.remove_required_pull_request_reviews()
示例#13
0
from os import environ
from utils import get_env_var
import asyncio

from crawler import Crawler

if __name__ == '__main__':
    try:
        token = environ['API_KEY']
    except KeyError:
        exit(1)

    pg_user = get_env_var('POSTGRES_USER', 'postgres')
    pg_password = get_env_var('POSTGRES_PASSWORD', '')
    pg_database = get_env_var('POSTGRES_DB', 'postgres')
    pg_host = get_env_var('PGHOST', 'localhost')

    crawler = Crawler(token,
                      pg_user=pg_user,
                      pg_password=pg_password,
                      pg_database=pg_database,
                      pg_host=pg_host)

    print('Кроулер запущен')
    asyncio.get_event_loop().run_until_complete(crawler.run())
示例#14
0
def restricted_mailboxes_handler(email_summary, context):
    """
    Restricted Mailboxes for Amazon WorkMail

    Parameters
    ----------
    email_summary: dict, required
        Amazon WorkMail Message Summary Input Format
        For more information, see https://docs.aws.amazon.com/workmail/latest/adminguide/lambda.html

        {
            "summaryVersion": "2019-07-28",                         # AWS WorkMail Message Summary Version
            "envelope": {
                "mailFrom" : {
                    "address" : "*****@*****.**"                  # String containing from email address
                },
                "recipients" : [                                    # List of all recipient email addresses
                   { "address" : "*****@*****.**" },
                   { "address" : "*****@*****.**" }
                ]
            },
            "sender" : {
                "address" :  "*****@*****.**"                   # String containing sender email address
            },
            "subject" : "Hello From Amazon WorkMail!",              # String containing email subject (Truncated to first 256 chars)"
            "messageId": "00000000-0000-0000-0000-000000000000",    # String containing message id for retrieval using workmail flow API
            "invocationId": "00000000000000000000000000000000",     # String containing the id of this lambda invocation. Useful for detecting retries and avoiding duplication
            "flowDirection": "INBOUND",                             # String indicating direction of email flow. Value is either "INBOUND" or "OUTBOUND"
            "truncated": false                                      # boolean indicating if any field in message was truncated due to size limitations
        }

    context: object, required
    Lambda Context runtime methods and attributes

    Attributes
    ----------

    context.aws_request_id: str
         Lambda request ID
    context.client_context: object
         Additional context when invoked through AWS Mobile SDK
    context.function_name: str
         Lambda function name
    context.function_version: str
         Function version identifier
    context.get_remaining_time_in_millis: function
         Time in milliseconds before function times out
    context.identity:
         Cognito identity provider context when invoked through AWS Mobile SDK
    context.invoked_function_arn: str
         Function ARN
    context.log_group_name: str
         Cloudwatch Log group name
    context.log_stream_name: str
         Cloudwatch Log stream name
    context.memory_limit_in_mb: int
        Function memory

        https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html

    Returns
    -------
    Amazon WorkMail Sync Lambda Response Format
    For more information, see https://docs.aws.amazon.com/workmail/latest/adminguide/lambda.html#synchronous-schema
        return {
          'actions': [                                              # Required, should contain at least 1 list element
          {
            'action' : {                                            # Required
              'type': 'string',                                     # Required. Can be "BOUNCE", "DROP" or "DEFAULT"
              'parameters': { <various> }                           # Optional. For bounce, <various> can be {"bounceMessage": "message that goes in bounce mail"}
            },
            'recipients': list of strings,                          # Optional. Indicates list of recipients for which this action applies
            'default': boolean                                      # Optional. Indicates whether this action applies to all recipients
          }
        ]}

    """
    logger.info(email_summary)
    organization_id = utils.get_env_var('WORKMAIL_ORGANIZATION_ID')
    restricted_group = utils.get_env_var('RESTRICTED_GROUP_NAME')
    report_mailbox_address = os.getenv('REPORT_MAILBOX_ADDRESS')

    sender = email_summary['envelope']['mailFrom']
    recipients = email_summary['envelope']['recipients']
    flow_direction = email_summary['flowDirection']

    if flow_direction == 'INBOUND':
        # 1. First check if sender is an external email address
        is_sender_external = utils.filter_external([sender], organization_id) is not None
        if is_sender_external:
            # 2. Then filter out restricted recipients a.k.a the ones that have restricted mailboxes from all email recipients
            restricted_recipients = utils.filter_restricted(recipients, organization_id, restricted_group)
            if restricted_recipients:
                # 3. Bounce this email for all restricted recipients and allow for the rest
                logger.info(f"Received email from external source {sender} to restricted mailboxes {restricted_recipients}; bouncing! ")
                additional_recipients = [] # Tip: You may add any additional recipients you would like to send copy of this email
                if report_mailbox_address:
                    additional_recipients.append(report_mailbox_address)
                return {
                      'actions': [
                      {
                        'recipients': restricted_recipients,    # Bounce this email for restricted_recipients
                        'action' : { 'type': 'BOUNCE' }
                      },
                      {
                        'recipients': additional_recipients,    # For any additional recipients and;
                        'allRecipients': True,                  # for all the remaining recipients (i.e. except the ones in bounce action)
                        'action' : { 'type': 'DEFAULT' }        # let the email be sent normally
                      }
                    ]}

    elif flow_direction == 'OUTBOUND':
        # 1. First check if sender is restricted a.k.a sender has an restricted mailbox
        is_sender_restricted = utils.filter_restricted([sender], organization_id, restricted_group) is not None
        if is_sender_restricted:
            # 2. Then filter out external email addresses from all email recipients
            external_recipients = utils.filter_external(recipients, organization_id)
            if external_recipients:
                # 3. Finally bounce this email for all external recipients and allow for the rest
                logger.info(f"Restricted mailbox {sender} attempted to send to external recipient {external_recipients}; bouncing!")
                additional_recipients = [] # Tip: You may add any additional recipients you would like to send copy of this email
                if report_mailbox_address:
                    additional_recipients.append(report_mailbox_address)
                return {
                      'actions': [
                      {
                        'recipients': external_recipients,      # Bounce this email for external recipients
                        'action' : {
                          'type': 'BOUNCE',
                          'parameters': { 'bounceMessage': "Sending e-mails to external domains is against company policy." }
                        },
                      },
                      {
                        'recipients': additional_recipients,    # For any additional recipients and;
                        'allRecipients': True,                  # for all the remaining recipients (i.e. except the in bounce action)
                        'action' : { 'type': 'DEFAULT' }        # let the email be sent normally
                      }
                    ]}

    else:
        error_msg = f"Received invalid flow direction:{flow_direction} in message summary"
        logger.error(error_msg)

    return {
          'actions': [
          {
            'allRecipients': True,                  # For all recipients
            'action' : { 'type' : 'DEFAULT' }       # let the email be sent normally
          }
        ]}
示例#15
0
 def test_existed_var(self):
     """This function tests if the config file for the translation server exists"""
     self.assertIsInstance(utils.get_env_var("FLASKKEY"), str, "get_env_var() could not find a flask Key")
def translate_handler(event, context):
    """
    Translate Email Content Using WorkMail Lambda Integration

    Parameters
    ----------
    email_summary: dict, required
        Amazon WorkMail Message Summary Input Format
        For more information, see https://docs.aws.amazon.com/workmail/latest/adminguide/lambda.html

        {
            "summaryVersion": "2019-07-28",                         # AWS WorkMail Message Summary Version
            "envelope": {
                "mailFrom" : {
                    "address" : "*****@*****.**"                  # String containing from email address
                },
                "recipients" : [                                    # List of all recipient email addresses
                   { "address" : "*****@*****.**" },
                   { "address" : "*****@*****.**" }
                ]
            },
            "sender" : {
                "address" :  "*****@*****.**"                   # String containing sender email address
            },
            "subject" : "Hello From Amazon WorkMail!",              # String containing email subject (Truncated to first 256 chars)"
            "messageId": "00000000-0000-0000-0000-000000000000",    # String containing message id for retrieval using workmail flow API
            "invocationId": "00000000000000000000000000000000",     # String containing the id of this lambda invocation. Useful for detecting retries and avoiding duplication
            "flowDirection": "INBOUND",                             # String indicating direction of email flow. Value is either "INBOUND" or "OUTBOUND"
            "truncated": false                                      # boolean indicating if any field in message was truncated due to size limitations
        }

    context: object, required
    Lambda Context runtime methods and attributes. See https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html

    Returns
    -------
    Amazon WorkMail Sync Lambda Response Format
    For more information, see https://docs.aws.amazon.com/workmail/latest/adminguide/lambda.html#synchronous-schema
        return {
          'actions': [                                              # Required, should contain at least 1 list element
          {
            'action' : {                                            # Required
              'type': 'string',                                     # Required. Can be "BOUNCE", "DROP" or "DEFAULT"
              'parameters': { <various> }                           # Optional. For bounce, <various> can be {"bounceMessage": "message that goes in bounce mail"}
            },
            'recipients': list of strings,                          # Optional. Indicates list of recipients for which this action applies
            'default': boolean                                      # Optional. Indicates whether this action applies to all recipients
          }
        ]}

    """

    logger.info(f"Received event: {event}")
    message_id = event['messageId']
    try:
        # 1. Download email
        downloaded_email = utils.download_email(message_id)
        # 2. Detect email language
        text_body = utils.extract_text_body(downloaded_email)
        # Use first 100 characters of email body and email subject to detect email source language
        email_language = translate_helper.detect_language(
            f"{event['subject']} {text_body[:100]}")
        if email_language != utils.get_env_var('DESTINATION_LANGUAGE'):
            # 3. Translate email
            translated_email = utils.translate_email(downloaded_email,
                                                     event['subject'],
                                                     email_language, text_body)
            # 4. Send translated email back to WorkMail
            utils.update_workmail(message_id, translated_email)
        else:
            logger.info('Email is already in destination language')
    except ClientError as e:
        if e.response['Error']['Code'] == 'MessageFrozen':
            # Redirect emails are not eligible for update, handle it gracefully.
            logger.info(
                f"Message {message_id} is not eligible for update. This is usually the case for a redirected email"
            )
        else:
            logger.error(e.response['Error']['Message'])
            if e.response['Error']['Code'] == 'ResourceNotFoundException':
                logger.error(
                    f"Message {message_id} does not exist. Messages in transit are no longer accessible after 1 day"
                )
            elif e.response['Error']['Code'] == 'InvalidContentLocation':
                logger.error(
                    'WorkMail could not access the updated email content. See https://docs.aws.amazon.com/workmail/latest/adminguide/update-with-lambda.html'
                )
            raise (e)

    # Resume normal email flow
    return {
        'actions': [{
            'action': {
                'type': 'DEFAULT'
            },
            'allRecipients': 'true'
        }]
    }