diff --git a/Python/egroup-to-channel.Python/README.md b/Python/egroup-to-channel.Python/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..52f3503b0a89a8c75c5c42403a843af61ec384b8
--- /dev/null
+++ b/Python/egroup-to-channel.Python/README.md
@@ -0,0 +1,22 @@
+# egroup-to-channel Python script
+
+Tool to migrate egroups to Notification service Channels.
+
+## Create a Channel for the specified Egroup
+- With the corresponding grappa group as channel member
+- With email posting permissions for egroup
+
+Steps:
+- Needs https://gitlab.cern.ch/authzsvc/tools/auth-get-sso-cookie/
+    - Works on LXPLUS for example
+- Edit ```clientapp_name``` and ```audience``` in ```get_api_token.py``` depending on your target (dev, qa, prod)
+
+Samples:
+- Migrate one egroup retrieving information from xldap: ```./egroup_to_channel.py -g it-dep-cda-wf -ldap```
+- Migrate one egroup specifying information: ```./egroup_to_channel.py -g it-dep-cda-wf -a it-dep-cda-wf-admins -o awagner -d "All members of IT-CDA-WF"```
+
+
+## Disable Egroup posting to Exchange 
+Run Mail team (sympa) egroup migration script to:
+- Stop sending mails to all egroup members via Exchange
+- Relay the egroup mails to the Notification Channel email address
\ No newline at end of file
diff --git a/Python/egroup-to-channel.Python/egroup_from_ldap.py b/Python/egroup-to-channel.Python/egroup_from_ldap.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a279f99f9ea394bbe4b36f58871dd61cca29ef8
--- /dev/null
+++ b/Python/egroup-to-channel.Python/egroup_from_ldap.py
@@ -0,0 +1,30 @@
+import ldap
+import json
+
+def egroup_from_ldap(egroup):
+    l = ldap.initialize("ldap://xldap.cern.ch")
+    try:
+        l.protocol_version = ldap.VERSION3
+        l.set_option(ldap.OPT_REFERRALS, 0)
+    
+        #bind = l.simple_bind_s("me@example.com", "password")
+    
+        base = "OU=Workgroups,DC=cern,DC=ch"
+        criteria = "(&(objectClass=group)(sAMAccountName=" + egroup + "))"
+        attributes = ['cn', 'description', 'managedBy', 'extensionAttribute6']
+        result = l.search_s(base, ldap.SCOPE_SUBTREE, criteria, attributes)
+    
+        results = [entry for dn, entry in result if isinstance(entry, dict)]
+        #print(results)
+
+        return {
+            'name': str(results[0]['cn'][0], 'utf-8'),
+            'description': str(results[0]['description'][0], 'utf-8'),
+            'adminGroup': str(results[0]['extensionAttribute6'][0], 'utf-8'),
+            'owner': str(results[0]['managedBy'][0], 'utf-8').replace(',OU=Users,OU=Organic Units,DC=cern,DC=ch', '').replace('CN=', ''),
+        }
+
+    # except:
+    #     return None
+    finally:
+        l.unbind()
\ No newline at end of file
diff --git a/Python/egroup-to-channel.Python/egroup_to_channel.py b/Python/egroup-to-channel.Python/egroup_to_channel.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e1ba903cb37f77cba0cd53b03cd127c0cc5eecc
--- /dev/null
+++ b/Python/egroup-to-channel.Python/egroup_to_channel.py
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+
+import requests
+import json, os, re
+import sys, getopt
+from get_api_token import get_api_token
+from egroup_from_ldap import egroup_from_ldap
+
+
+BACKEND_URL='https://api-notifications-dev.app.cern.ch'
+#BACKEND_URL='https://localhost:8080'
+ACCESS_TOKEN=get_api_token()
+HEADER={'Authorization': 'Bearer ' + ACCESS_TOKEN}
+VERIFY=False # Verify SSL certificate for requests
+
+# Create new Channel
+def create_channel(egroup, admingroup, description):
+    print('Creating Channel')
+    data = {'channel': {
+        'name': egroup,
+        'slug': re.sub('[^0-9a-z-_]', '-', egroup.lower()),
+        'description': description + ' - Migrated from Egroups',
+        'adminGroup': { 'groupIdentifier': admingroup },
+        'visibility': 'RESTRICTED',
+        'submissionByForm': [ 'ADMINISTRATORS' ],
+        'submissionByEmail': [ 'EGROUP' ],
+        'incomingEgroup': egroup + '@cern.ch',
+    }}
+    #print(data)
+    r = requests.post(BACKEND_URL + '/channels/', json=data, headers=HEADER, verify=VERIFY)
+    if r.status_code != requests.codes.ok:
+        print('error creating channel', r.json())
+        sys.exit(2)
+    new_channel = r.json()
+    #print(new_channel)
+
+    return new_channel['id']
+
+# Add egroup as Channel Member
+def add_egroup_to_channel(channel_id, egroup):
+    print('Adding group to Channel members', egroup)
+    data = { 'group': { 'groupIdentifier': egroup } }
+    r = requests.put(BACKEND_URL + '/channels/' + channel_id + '/groups', json=data, headers=HEADER, verify=VERIFY)
+    if r.status_code != requests.codes.ok:
+        print('error updating channel', r.json())
+        sys.exit(2)
+    updated_channel = r.json()
+
+    return updated_channel['id']
+
+# Remove ME from Members
+def remove_me_from_channel(channel_id):
+    print('Removing ME from Channel members')
+    r = requests.get(BACKEND_URL + '/me', headers=HEADER, verify=VERIFY)
+    if r.status_code != requests.codes.ok:
+        print('error removing ME from channel', r.json())
+        sys.exit(2)
+    me = r.json()
+    if not me['userId']:
+        print('error retrieving ME', me)
+        sys.exit(2)
+
+    data = { 'userId': me['userId'] }
+    r = requests.delete(BACKEND_URL + '/channels/' + channel_id + '/members', json=data, headers=HEADER, verify=VERIFY)
+    if r.status_code != requests.codes.ok:
+        print('error removing ME from channel members', r.json())
+        sys.exit(2)
+    updated_channel = r.json()
+
+    return updated_channel['id']
+
+# Change Channel owner
+def set_channel_owner(channel_id, username):
+    print('Setting Channel owner to', username)
+    data = { 'username': username }
+    r = requests.put(BACKEND_URL + '/channels/' + channel_id + '/owner', json=data, headers=HEADER, verify=VERIFY)
+    if r.status_code != requests.codes.ok:
+        print('error setting channel owner', r.json())
+        sys.exit(2)
+    updated_channel = r.json()
+
+    return updated_channel['id']
+
+def usage():
+    print('egroup-to-channel.py -g <egroupname> [-a <admingroup> -o <owner> -d <description> -ldap]')
+    print('\t-g <egroupname> is required')
+    print('\t-ldap will search in ldap for admingroup, owner and description information')
+
+# Main
+def main(argv):
+    egroup = ''
+    adminGroup = ''
+    owner = ''
+    description = ''
+    enableldap = False
+    try:
+        opts, args = getopt.getopt(argv, "lhg:a:d:o:", ["group=", "admingroup=", "description=", "owner=", "ldap"])
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+    for opt, arg in opts:
+        if opt == '-h':
+            usage()
+            sys.exit()
+        elif opt in ("-g", "--group"):
+            egroup = arg
+        elif opt in ("-a", "--admingroup"):
+            adminGroup = arg
+        elif opt in ("-o", "--owner"):
+            owner = arg
+        elif opt in ("-d", "--description"):
+            description = arg
+        elif opt in ("-l", "--ldap"):
+            enableldap = True
+    if not egroup:
+        usage()
+        sys.exit(2)
+
+    # Remove @cern.ch if any
+    egroup = egroup.replace('@cern.ch', '')
+
+    if enableldap:
+        print("Retrieving egroup information from Xldap")
+        ret = egroup_from_ldap(egroup)
+        if ret:
+            owner = ret['owner']
+            adminGroup = ret['adminGroup']
+            description = ret['description']
+
+    print('Creating Channel from Egroup')
+    print('\tName: ', egroup)
+    print('\tOwner: ', owner)
+    print('\tAdmin group: ', adminGroup)
+    print('\tDescription: ', description)
+
+    if not owner:
+        print("Missing owner username")
+        usage()
+        sys.exit(2)
+
+    # Get bearer
+    # ACCESS_TOKEN=get_api_token()
+    # HEADER={'Authorization': 'Bearer ' + ACCESS_TOKEN}
+
+    # Create Channel
+    channel_id = create_channel(egroup, adminGroup, description)
+    if not channel_id:
+        print('Error creating channel ', egroup)
+        sys.exit()
+    # Add corresponding Grappa group to members
+    add_egroup_to_channel(channel_id, egroup)
+    # Remove me from members
+    remove_me_from_channel(channel_id)
+    # Change owner to egroup owner
+    set_channel_owner(channel_id, owner)
+
+if __name__ == "__main__":
+   main(sys.argv[1:])
diff --git a/Python/egroup-to-channel.Python/get_api_token.py b/Python/egroup-to-channel.Python/get_api_token.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e43cb5cec3628393ddade91c49d5e3cb915df3a
--- /dev/null
+++ b/Python/egroup-to-channel.Python/get_api_token.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+from auth_get_sso_cookie import cern_sso
+import subprocess
+import requests
+
+AUTH_HOSTNAME = "auth.cern.ch"
+AUTH_REALM = "cern"
+
+################# CONFIGURATION ##################
+
+# The Client application (application portal, option my app cannot keep a secret)
+#clientapp_name = "tmp-push-notifications-clientscript"
+clientapp_name = "notifications-dev-clientapi"
+# clientapp_name = "notifications-qa-clientapi"
+# clientapp_name = "notifications-clientapi"
+
+# Standard localhost uri for this virtual app
+clientapp_uri = "https://localhost"
+
+# The target application (the backend API), with granted permissions to client application for token exchange
+#audience = "tmp-push-notifications"
+audience = "notifications-dev"
+# audience = "notifications-qa"
+# audience = "notifications"
+
+##################################################
+
+#if __name__ == "__main__":
+def get_api_token():
+    # Get Token for the clientscript application
+    # Using https://gitlab.cern.ch/authzsvc/tools/auth-get-sso-cookie/
+    # Run with parameters for the clientscript application 
+    # clientapi.py -u https://localhost -c tmp-push-notifications-clientscript
+    #token = command_line_tools.auth_get_sso_token()
+    # proc = subprocess.Popen(
+    #     ["auth-get-sso-token", "-u", clientapp_uri, "-c", clientapp_name], 
+    #     stdout=subprocess.PIPE, 
+    #     stderr=subprocess.STDOUT)
+    # token = proc.communicate()[0].rstrip()
+    token = cern_sso.get_sso_token(clientapp_uri, clientapp_name, True, AUTH_HOSTNAME, AUTH_REALM)
+    #print("TOKEN to exchange retrieved")
+    #print(token)
+
+    # Do Token Exchange for the Backend API application
+    # https://auth.docs.cern.ch/user-documentation/oidc/exchange-for-api/
+    r = requests.post(
+            "https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/token",
+            data={
+                "client_id": clientapp_name,
+                "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
+                "subject_token": token,
+                "requested_token_type": "urn:ietf:params:oauth:token-type:refresh_token",
+                "audience": audience,
+            },
+        )
+    if not r.ok:
+        print(
+            "The token response was not successful: {}".format(r.json()))
+        r.raise_for_status()
+
+    token_response = r.json()
+    access_token = token_response["access_token"]
+    #print("access_token retrieved")
+    #print(access_token)
+    return access_token
+
+    # Then calls to the backend can be performed with this access token
+    # ACCESS_TOKEN=$(python get-api-token.py)
+    # curl -X GET "https://api-notifications-dev.app.cern.ch/channels/" -H  "authorization: Bearer $ACCESS_TOKEN"
+