- Setup (1/2):
Bash
: Configure NGINX, WSGI and Certbot.- Finish Bash script and/or port to a Python script.
- Unit tests.
- Draft a simple CI/CD pipeline.
- Middleware (1/2):
- Form data validation.
- Better error handling.
- Frontend:
- Complete form fields and commands
- Responsive (Bootstrap)
- Thoroughly test AJAX requests
- Setup (2/2):
- Design as a pipeline with inputs and outputs, covering all deployment stages.
- Automate configuration and deployment
- Deploy as a Docker image.
- Middleware (2/2):
- Implement API gateway. Test full workflow (e.g. with Postman).
- Core:
- Testing (with Middleware and frontend).
- 1 or 2 more
BaseLogger
sub-classes. query()
method forBaseLogger
class.
- README:
main()
flowchart- Setup pipeline diagram
- It would be better to deploy the Core (Apps Script) app using the Google API Python client library instead of
clasp
. This saves ~100 MB of disk space and leverages installed project (Middleware app's) dependencies. Some useful resources in this regard:
- Studying project setup as a pipeline to make the process as automatable as possible, and the Bash script easily convertible.
- Resources:
- Further enhancements of the setup script. Published a new execution demo as a asciicast, and added a sample output (as a HTML file).
- Revised the "Setup" section of the README file.
- Plans for upcoming updates of the setup script
- Interactive or automatic (retries...).
- Enable/Disable debugging; Levels: Info/Success, Warning, Error.
- Revert changes back in case of an error.
- User should have read/write access rights to the current directory.
- User should be a 'sudoer'.
- About
clasp deploy
and Google Apps Script project versions (summary for future reference):- It is possible to create immutable versions of a script.
- Each version can have multiple deployments (each with its own deployment ID) or redeployed (which create a new deployment with a new ID).
- When a Apps Script project is redeployed using an existing deployment ID, a new version is automatically created.
- Interesting resources:
- Up to now,
setup_centos7.sh
deploys Gmail AutoResponder web-app in Development/Test mode. - Published an asciicast: Gmail AutoResponder - Dev/Test Deployment
- Added sample execution output of
setup_centos7.sh
on a base CEntOS 7 system (setup_centos7_output.html
). - Revising "Setup and Run" section of README.
Been developing, enhancing and thoroughly testing a setup bash script: setup_centos7__draft.sh
.
- The
setup__draft.sh
can now be pretty much run for a guided and semi-automatic initial configuration. The script is still unfinished though. - I believe it makes some sense to try first to configure and deploy the code manually (using Bash) before using a CI/CD or a configuration management tool.
- Online resources:
- [EPEL] How to install Python 3.6 on CentOS 7
- nginx + uwsgi + virtual environment. What goes inside?
- Restructuring Django’s Settings
- SQLite version issue:
-
Chosen solution:
-
$ wget https://www.sqlite.org/2018/sqlite-autoconf-3240000.tar.gz $ tar zxvf sqlite-autoconf-3240000.tar.gz $ ./configure --prefix=/usr/local $ make $ sudo make install $ python3.6 -c "import sqlite3; print(sqlite3.sqlite_version)" 3.7.17 $ export LD_LIBRARY_PATH=/usr/local/lib $ python3.6 -c "import sqlite3; print(sqlite3.sqlite_version)" 3.24.0
$ cd ~ $ wget https://www.sqlite.org/2019/sqlite-autoconf-3290000.tar.gz $ tar zxvf sqlite-autoconf-3290000.tar.gz cd sqlite-autoconf-3290000 $./configure --prefix=$HOME/opt/sqlite $ make && make install export PATH=$HOME/opt/sqlite/bin:$PATH export LD_LIBRARY_PATH=$HOME/opt/sqlite/lib export LD_RUN_PATH=$HOME/opt/sqlite/lib sqlite3 --version 3.29.0 2019-07-10 17:32:03
-
Trying to upgrade SQLite 3.7.17 to version 3.8 on CentOS 7 for MediaWiki : linuxadmin
$ wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.8.11/1.fc21/x86_64/sqlite-devel-3.8.11-1.fc21.x86_64.rpm $ wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.8.11/1.fc21/x86_64/sqlite-3.8.11-1.fc21.x86_64.rpm $ sudo yum install sqlite-3.8.11-1.fc21.x86_64.rpm sqlite-devel-3.8.11-1.fc21.x86_64.rpm $ sqlite3 --version 3.8.11 2015-07-27 13:49:41 b8e92227a469de677a66da62e4361f099c0b79d0
-
RPM Fedora 19 sqlite 3.8.3 x86_64 rpm
-
Fedora repositories are not likely to be compatible with CentOS. Repositories for other Enterprise Linux distros derived from the same upstream sources are more likely to be compatible, but should still be used with care. - Source
-
-
atomic-sqlite-3.8.5-6803.el7.art.x86_64.rpm CentOS 7 Download
-
Be particularly careful about the Atomic repo as they enable their repo by default when installed, and overwrite user changes in the configuration without notice when the release package is updated. Atomic will replace many core packages as configured when installed. - Source
-
-
- Been Drafting and pseudo-coding the steps of the Setup & Run (Provision, Configure and Deploy) process in a separate temporary file (
setup__draft.sh
). Testing on a CEntOS 7 VM. - Restructured the repo.
- Overall revisions of the README file and the Setup process (still in progress).
- Noted some useful resources:
Drafting the "Setup and Run" part of the README and updating code accordingly.
- Been thoroughly testing Core app features.
- Added and tested/verified more default
rawContent
email filters (for automatic Delivery Reports):Content-Type: multipart/report
report-type=delivery-status
Content-Type: message/delivery-status
- Temporarily removed
webapp
key fromappsscript.json
.
- Added TestApp deployment option, by providing a test email address passed as a second argument to the
initSettings()
function, which will enable the app right after the initialization process, but will start sending test automatic responses to the provided email address instead of the original senders. - Added a wrapper function
autoReply()
which will either callreplyTest()
orreplyToThread()
, depending on the value and validity of thetestEmail
script user property. checkSpreadsheetById(id)
method of theGSpreadsheetLogger
now additionally checks if the Spreadsheet is trashed.raw_notes.md
updated and renamedTODO.md
for convenience, as a number of issues have already been addressed.- Some
main.js
refinements.
An entirely revised README, along with an App architecture diagram and some minor code modifications.
- Further refactoring and modularization of Apps Script code; Core app is now pretty stable (as far as I can tell). Key updates:
- Class-based implementation of application loggers. Thus, we'll avoid hard-coding features and make future implementations of other logs databases easier. For instance, initializing a logs database would generally mean:
- Connect with Read/Write permissions to an existing instance, or create a new one.
- Create two data collections (e.g.:
GSpreadsheets
: sheets,SQL
: tables,DocumentDB
; collections), each with the specified data fields (e.g.:GSpreadsheets
: columns/header,SQL
: columns,DocumentDB
; fields). - Logs would then be stored as single entries or 2D datasets (e.g.:
GSpreadsheets
: rows,SQL
: records,DocumentDB
; documents).
- Revised list of App settings:
-
IS_GSUITE_USER
:Boolean
. -
enableApp
:Boolean
. (default:'false'
). -
coreAppEditUrl
:String
. Edit URL of the Apps Script project. -
filters
:JSON string
; Message content filters.default:
{ "rawContent": ['report-type=disposition-notification'], "from": [ '(^|<)((mailer-daemon|postmaster)@.*)', 'noreply|no-reply|do-not-reply', '.+@.*\\bgoogle\\.com', Session.getActiveUser().getEmail() ], "to": ['undisclosed-recipients'] }
-
logger
:JSON string
.identifiers
property of anAppLogger
class instance.Example:
{ "id": 'ABCDEF', "viewUri": "https://www.xxxx.yy/ABCDEF?view", "updateUri": "https://www.xxxx.yy/ABCDEF?update" }
-
timeinterval
:Integer
. (default:10
). -
starthour
:Integer
. (default:17
). -
finishhour
:Integer
. (default:8
). -
utcoffset
:Integer
. (default:0
). -
ccemailadr
:String
, one or a RFC-compliant comma-separated list of email addresses. (default:''
). -
bccemailadr
:String
, one or a RFC-compliant comma-separated list of email addresses. (default:''
). -
noreply
:Boolean
; whether or not to reply with anoreply@
email address. (default:0
ifIS_GSUITE_USER
==='true'
,2
otherwise). -
msgbody
:String
; Response message body in HTML format. (default:getDefaultMessageBody()
function return value).
-
- Logical and functional organization of JavaScript libraries/files for better code reusability.
- Class-based implementation of application loggers. Thus, we'll avoid hard-coding features and make future implementations of other logs databases easier. For instance, initializing a logs database would generally mean:
- Added license file (MIT License).
- Noting some interesting resources:
A massive refactoring of the whole core/Apps Script code: the code is now cleaner and more modularized. A significant number of tests have been made along the way, but runtime (system/end-to-end) tests are still needed:
- Several data parsing, serializing and validation features.
Code.js
file andautReply()
function have been respectively renamedmain.js
andmain()
.- These functions replaced blocks of code in
main.js
(formerlyCode.js
):filterMessage(gmailMessage, filters)
,getLastMessage(gmailThread)
,replyToThread(gmailThread)
,appLogger(logEntries, target)
. - Core app functions are now split into three files (libraries):
gmail-autoresponder.js
,loggers.js
andfilters.js
. - Message filters are now stored as serialized JSON objects into Script user properties.
- Added a wrapper function
appLogger()
that can target (given the right parameters) log databases other that Google Spreadsheets (like document DBs accepting HTTP requests as queries...). - Final list of App Settings that will be stored as Script user properties:
firstTimeRun
: Boolean (instead ofINIT_ALREADY_RUN
)IS_GSUITE_USER
: BooleanenableApp
: Booleanfilters
: JSON string: default:{ "rawContent": ['report-type=disposition-notification'], "from": [ '(^|<)((mailer-daemon|postmaster)@.*)', 'noreply|no-reply|do-not-reply', '.+@.*\\bgoogle\\.com', Session.getActiveUser().getEmail() ], "to": ['undisclosed-recipients'] }
logs
: JSON string: example:{ "type": "gspreadsheet", "identifiers": { "id": "XXXXXXXXXXXX", "url": "https://docs.google.com/spreadsheets/d/XXXXXXXXXXXX/edit#gid=0" } }
starthour
: Integerfinishhour
: Integerutcoffset
: Integerccemailadr
: String, comma-separated email addresses (RFC-compliant)bccemailadr
: String, comma-separated email addresses (RFC-compliant)noreply
: Booleanmsgbody
: String, HTML/Text
Core (Apps Script) code is still broken, as it is being refactored:
- Removed Google Sheets formatting code from
app/core/Code.js
. - Updated functions:
getSettings()
andsetProperties(objParams)
, ofapp/core/gmail-autoresponder.js
. - Renamed script user properties, to keep the same names between
core
,backend
andfrontend
parts of the code. - Converting blocks of code to reusable functions.
- Exploring possible ways to modularize logging features (of executed sessions and processed messages), by abstracting format (JSON...), log entry data structure and target (Google Sheets, store to a Document DB through a JSON POST request...). I'm thinking of the following inheritance mechanism of both
SessionLogger
andProcessedMessageLogger
objects, from a parent objectAppLogger
. Intended structure / Pseudo-code (check next worklog entry for update) :AppLogger
(Parent Object):date
,append(entry|arrayOfEntries)
,getAllEntries()
,target = {}
SessionLogger
(Child Object):dateExecuted = super.date
,numberOfThreads
,append(entry|arrayOfEntries) = super.append()
,getAllEntries() = super.getAllEntries()
,target = super.target
ProcessedMessageLogger
(Child Object):dateReceived = super.date
,sentRepDate
,messageId
,threadId
,messageFrom
,messageSubject
,appliedFilter
,append(entry|arrayOfEntries) = super.append()
,getAllEntries() = super.getAllEntries()
,target = super.target
.
- Using Django Forms as a "form data validation middleware":
- Significant revision of core (Apps Script), backend (Django views) and frontend (Django templates) code.
- Multi-level error handling for form submit via AJAX POST request:
- HTTP request
- Django Forms data validation
- Apps Script API
- Apps Script core app
- Significant code refinement, and even refactoring, on both core (Apps Script) and backend (Python/Django) parts:
- Enhanced error handling when loading settings from Apps Script app using AJAX POST requests and Django Forms.
- Removed non used functions from
app\core\gmail-autoresponder.js
.
- Will come back later with more details about what has been enhanced so far. Code is still partially broken at the moment of writing this worklog entry; I just wanted to save the changes.
- Exploring Django Forms and HTML form submit using AJAX POST requests.
- Some Apps Script code refinement.
- Revised project repository structure (got rid of unneeded code).
-
Now the Django app can load settings from Apps Script backend into the frontend, through AJAX requests.
-
Handling access to some special URLs like
/auth
and/getsettings
. -
Refactoring
views.py
to use a function decorator@check_user_session
to check whether the user is authenticated or not.
- All of these scopes are required if you want to get full information about the logged in Google user:
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
openid
- Customizing 'Views' to add an endpoint URL for Ajax requests (with JSON responses).
2021-02-15 (code)
- Added working demo code (
app/backend-python-demo
) of a Django app that runs a function of a Google Apps Script project deployed as API-Executable, through the Google Apps Script API, using Google API Python client. Also included target Apps Script project code (app/backend-python-demo/target_appsscript_code.js
) - The code is built upon the AuthLib library demo for Django and the Google Apps Script API Python Quickstart example:
- I dropped the AuthLib library after many tries, due to confusing instructions about OAuth2 refresh token support for Django:
- python - Getting refresh_token with lepture/authlib - Stack Overflow
-
client_credentials
won't issue refresh token. You need to use authorization_code flow to get the refresh token.- A OAuth server-side configuration seems to be needed: stackoverflow.com/questions/51305430/…
-
- Refresh and Auto Update Token · Issue #245 · lepture/authlib
-
Also be aware, unless you're on authlib 0.14.3 or later, the django integration is broken for refresh (If you're using the metadata url): RemoteApp.request fails to use token_endpoint to refresh the access token · Issue #193 · lepture/authlib
-
- python - Getting refresh_token with lepture/authlib - Stack Overflow
- Analyzing the original Django demo code (that has been later modified to use
google-api-python-client
andgoogle-auth-oauthlib
instead ofauthlib
):- Wrap up: The Django app will act as a API Gateway, so we don't need persistent storage of user information or account. The app will have access to Google user's resources until he logs out, or his session is expired or invalidated. After that, all existing data (i.e. browser cookies and session data in the database) is removed.
- 'Diffing' changes between the default code of newly created Django app 'project' and that of the AuthLib Django demo:
project/settings.py
(modified):- Removed from default configuration:
INSTALLED_APPS
:'django.contrib.admin'
'django.contrib.staticfiles'
MIDDLEWARE
: (onlySessionMiddleware
was kept, which is enough for the intended use case.)'django.contrib.auth.middleware.AuthenticationMiddleware'
'django.contrib.messages.middleware.MessageMiddleware'
- Removed from default configuration:
project/urls.py
(modified):- Understandably, all things related to the
django.contrib.admin
app were removed. Here is the full content ofproject/urls.py
:# from django.contrib import admin from django.urls import path from project import views urlpatterns = [ # path('admin/', admin.site.urls), path('', views.home), path('login/', views.login), path('auth/', views.auth, name='auth'), path('logout/', views.logout), # Added ]
- Understandably, all things related to the
project/views.py
(created): This is practically the only file that I needed to modify later, in order to port the demo to usegoogle-api-python-client
andgoogle-auth-oauthlib
:import json from django.urls import reverse from django.shortcuts import render, redirect from authlib.integrations.django_client import OAuth CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration' oauth = OAuth() oauth.register( name='google', server_metadata_url=CONF_URL, client_kwargs={ 'scope': 'openid email profile' } ) def home(request): user = request.session.get('user') if user: user = json.dumps(user) return render(request, 'home.html', context={'user': user}) def login(request): redirect_uri = request.build_absolute_uri(reverse('auth')) return oauth.google.authorize_redirect(request, redirect_uri) def auth(request): token = oauth.google.authorize_access_token(request) user = oauth.google.parse_id_token(request, token) request.session['user'] = user return redirect('/') def logout(request): request.session.pop('user', None) return redirect('/')
project/templates/home.html
(created):{% if user %} <pre> {{ user }} </pre> <hr> <a href="/logout/">logout</a> {% else %} <a href="/login/">login</a> {% endif %}
- Google Apps Script API Python Quickstart code adaptation:
credentials.json
contains the Client ID credentials (file downloadable from the GCP console) of the GCP project associated to the target Apps Script appFlow
class was used instead ofInstalledAppFlow
.- Instead of using pickles, OAuth2 token (a
Credentials
instance) is converted to a dictionary and saved to session (as a session key namedtoken
). - Value of session key
user
(which identifies the logged in user) is retrieved by parsing the Open ID Connect ID Token contained in the Credentials object resulting from a complete and successful OAuth2 flow.
- I dropped the AuthLib library after many tries, due to confusing instructions about OAuth2 refresh token support for Django:
- Installing required libraries, creating the database and launching the dev server:
cd app/backend-python-demo pip install Django google-api-python-client google-auth-oauthlib python manage.py migrate python manage.py runserver
- Key online resources used:
Flow
class of thegoogle_auth_oauthlib.flow
module (google-auth-oauthlib 0.4.1 documentation)Flow.fetch_token(code=code)
Flow.credentials
- Constructs a
google.oauth2.credentials.Credentials
class.- ..which is a child class of
google.auth.credentials.Credentials
to_json()
: Returns A JSON representation of this instance. When converted into a dictionary, it can be passed tofrom_authorized_user_info()
to create a new Credentials instance.id_token
: can be verified and decoded (parsed) usinggoogle.oauth2.id_token.verify_oauth2_token()
- ..which is a child class of
- Constructs a
- I have been extensively checking and testing some Python backend code (OAuth authentication, Apps Script API client...). I will share my findings in this repository as soon as I come with some significant results. So far I updated
2021-01-30
entry with a few details and analysis points.
- Code refinements. Front HTML (
index.html
) will provide links to Filters and Logs spreadsheets when loading settings. - Three deployment options/scenarios to be considered:
- Self-contained: Deployed as a web application, with both backend and frontend components served by the Apps Script project. The user should always have an active Google account session (already logged in to Gmail, for example) on the browser in order to access the webapp by its URL.
- Webapp as a API gateway: Deployed as a web application, with all required sharing settings, and acting as a API gateway to all Google services being used. In other words, the deployed webapp will play the role of an app-level API, abstracting away features and technical requirements specific to each Google service (Gmail, Spreadsheets, Drive) and emulating a simple RESTful API using
doGet()
anddoPost()
methods; this API will then be "consumable" by any third-party app (serving as backend/frontend) as long as it supports OAuth2 authentication to Google services. In this case, we can just keep the default (Apps Script-managed) GCP project our Apps Script project would be automatically associated to. No need to switch to a standard (user-managed) one.- That said, we might actually still need a GCP project to identify and authenticate our Apps Script webapp users, i.e. users that have been previously granted the permission to execute by sharing the project with them. As far as I can tell, the Apps Script project doesn't have to be associated to that GCP project. In short, a bearer token (identifying the user) will be required in each POST or GET request sent to the webapp URL. This "bearer authentication" process is often taken care of by some OAuth library of the backend language/framework being used.
- Setting the webapp to always execute as its owner while sharing it with multiple users, might be useful for centralizing configurations and logs; if the project is well designed to concurrently read from and write to Google spreadsheets, within Apps Script services daily quotas and limitations (that are subject to change at any time, without notice).
- API-executable: Deployed as API-executable, after associating our Apps Script project to a standard (user-managed) GCP project, which would be an extra step that could be undesirable in some use cases. That said, the application will be manageable with just 3 functions:
appinit()
,getSettings()
andsetProperties()
; all run (either directly or using a client library) through the well documented Apps Script API, using thescript.run
method.- Unlike webapp deployments, Apps Script projects deployed as "API-Executable" can only be executed as the user accessing them. This was confirmed after successfully executing this example code.
-
Triggers of manually deleted Apps Script projects will keep running unless their files are removed from Drive's trash. Even then, these triggers would still show on
https://script.google.com/home/triggers
as anonymous/blank.That is, in fact, what caused the error I noted on 2020-05-25:
Authorization is required to perform that action
. So, to avoid this, we have to delete the triggers programmatically, or through "My Triggers - Apps Script" page. -
Planning to refactor both project's structure and code, by splitting it into four components:
- Core: Google Apps Script code (deployed as a webapp) as a managed "API gateway" to all required Google services (AppsScript, Gmail, Drive, Sheets).
- Backend: as a middleware for authenticated access to a GMail-AutoResponder instance.
- API: a API wrapper that will interact with the
Core
through authenticated HTTP requests. - Frontend: likely to be rendered on the backend (by template engine). Bootstrap 4 will be used.
-
TODO:
- Add API documentation to README.
- Logging to text files:
- Use a common format for log entries: e.g.
[W 2021-01-05 11:57:42.715 ModuleName] Log message
- Log levels : App vs Infrastructure
- Use a common format for log entries: e.g.
- Basically, there are 3 building blocks of the problem to be addressed/implemented :
- Full OAuth2 flow.
- User session.
- (1+2) Turn stateless authenticated requests [i.e. POST requests each with an authentication bearer obtained after a successful OAuth flow] into stateful requests, by providing multi-user sessions [to let each user run his own instance of the Apps Script webapp against his Google account] and seamlessly process signup, login, logout and access token refresh operations.
- Suggested core packages to be used (along with any required dependencies) :
- google-auth-library, or passport-google-oauth
- express-session
- A basic session store, such as connect-session-sequelize or session-file-store.
- Possibly : body-parser, uuid
- Interesting resources :
-
I've decided to give the idea of "making an API proxy" another go, after checking @tanaikech's great write-up about GAS Web Apps. Here is the summary :
- The web app will be executed as the user accessing it, either the owner or any other Google user.
- The Apps Script project needs to be shared with any Google user that we would want to access the web app.
- The Google user accessing the web app is required to provide a OAuth access token as an authorization bearer in each GET or POST request sent to web app's URL.
- On first time access, the user will be redirected to a web page to grant access to the scopes required by the app.
-
Communicationg through GET and POST requests to a Google Apps Script web app from a third party application, NodeJS for instance : A good starter code is provided in the Google documentation. There are a couple of adjustments and customizations to consider :
- The example is geared towards CLI use, rather than in-browser use. So we'll need to modify the code to make it process web app OAuth credentials (with custom redirect URL...etc)
- Manage authentication tokens using sessions/cookies instead of reading/writing server-side files.
- Slightly redesign the authentication and the access workflow.
- A sample code (both GAS backend and client NodeJS) will be separately developed as a PoC.
-
Using
doPost()
to handle and process POST requests to the app seems to cause a lot of confusions and issues⁽¹⁾⁽²⁾⁽³⁾⁽⁴⁾⁽⁵⁾⁽⁶⁾ around :- CORS (Cross-Origin Resource Sharing)⁽⁷⁾⁽⁸⁾,
- web app access/permissions,
- head vs versioned deployments,
- possibly the used Apps Script runtime.
I don't want to resort to "hacky ways", so I think (for the time being) I'll just continue developing and refining functions as I have been and consider using Apps Script API to run them from any third party application.
-
I'm receiving daily a summary of a significant number of the same error message for a running instance of the application. Unfortunately, regular Stackdriver logs do not provide any useful information, so I think I'll associate the Script app to a GCP project to get access to advanced logging.
- Preparing code to test and progressively convert JavaScript client code to Ajax POST requests to be processed by the
doPost()
backend function. - Setting up a custom GCP project for a script gives you access to verbose and more informative logs on each function call, which turns out to be fairly useful when debugging.
- Migrated
raw_notes.md
's section about "scripted deployment of Apps Script projects" to DevOps Lab repository.
Since I often start working late at night, it is sometimes challenging to commit anything significant before midnight. So I'll leave this note here and be back a few hours later with more updates..
- For some reason, the format used for JavaScript files documentation header made functions unrecognized :
Corrected headers of the files
TypeError: google.script.run.withSuccessHandler(...).withFailureHandler(...).appinit is not a function
appinit.js
,Code.js
andgmail-autoresponder.js
.
- As I already pointed it out, I'm thinking about making the code base "convertible" to other backend language, and this is how I'm planning to proceed :
- Client vs server JavaScript libraries : Separate functions that run on the backend from those mainly executed client-side.
- Run Apps Script code as backend-only, and ultimately as a "API Proxy" : Deploy code as "API Executable"; All client-side tasks will be run through HTTP POST requests, i.e.
doPost()
will be the main entry point, instead of calling functions through thegoogle.script.run
API.
- Made the repository public.
-
Bringing together all the needed Materialize components to rebuild the frontend page.
-
Exploring Materialize website, checking and testing examples and sample codes.
...
- Exploring the possibility of building a fresh frontend with Materialize CSS framework, especially as there seem to be some good combinations with CKEditor Inline mode.
- Been testing app's behaviour when the added switch is turned on or off: the app stops/starts sending responses accordingly, just as expected/desired.
- I should consider adding emails from Google (like Apps Script 'Summary of failures' sent from
apps-scripts-notifications@google.com
) toFrom
filters. I had some test instances respond to these emails.
- Added parameter (script user property) with binary value ('YES'/'NO') to enable/disable the app. Default value is 'NO'.
- Refined a little bit
README.md
. TODO.md
content merged intoraw_notes.md
.
2020-05-10 (code)
-
First complete (backend and frontend) implementation of the cycle :
Initialize WebApp
➝Modify settings
➝Show updated settings
➝Reset WebApp
:- New 3rd party component :
SweetAlert2
, used instead of JavaScript'salert()
.
- New 3rd party component :
ScriptApp.getService().getUrl()
return values, WTF?
-
I had some fun checking and comparing return values of the Apps Script method
ScriptApp.getService().getUrl()
, depending on multiple factors :- Account type (Free vs G-Suite),
- Runtime (Legacy (Rhino, ES5) vs V8),
- Caller of the function :
- Manually, on the Apps Script Editor (
Logger.log(ScriptApp.getService().getUrl())
) - As a response to a GET request (
doGet()
) - Client-side (
google.script.run
)
- Manually, on the Apps Script Editor (
- Version of the deployed code (latest/dev vs specific/prod)
-
Conclusion: the returned URL is almost unpredictable!
- Script Editor :
Logger.log(ScriptApp.getService().getUrl())
- V8 Enabled :
https://script.google.com/macros/s/{Dev-Deployment-ID}/dev
- V8 Disabled :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- V8 Enabled :
- Deployed Web App :
- V8 Enabled :
- GET response
doGet()
:- Dev version (latest code deployed) :
https://script.google.com/macros/s/{Dev-Deployment-ID}/dev
- Prod version (specific version deployed) :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- Called client-side :
google.script.run
- Dev version (latest code deployed) :
https://script.google.com/macros/s/{Dev-Deployment-ID}/dev
- Prod version (specific version deployed) :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- GET response
- V8 Disabled :
- GET response
doGet()
:- Dev version (latest code deployed) :
https://script.google.com/macros/s/{Dev-Deployment-ID}/dev
- Prod version (specific version deployed) :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- Called client-side :
google.script.run
- Dev version (latest code deployed) :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- Prod version (specific version deployed) :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- GET response
- V8 Enabled :
- Script Editor :
Logger.log(ScriptApp.getService().getUrl())
- V8 Enabled :
https://script.google.com/macros/s/{Dev-Deployment-ID}/dev
- V8 Disabled :
https://script.google.com/a/mydomain.com/macros/s/{Prod-Deployment-ID}/exec
- V8 Enabled :
- Deployed Web App :
- V8 Enabled :
- GET response
doGet()
:- Dev version (latest code deployed) :
https://script.google.com/macros/s/{Dev-Deployment-ID}/dev
- Prod version (specific version deployed) :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- Called client-side :
google.script.run
- Dev version (latest code deployed) :
https://script.google.com/macros/s/{Dev-Deployment-ID}/dev
- Prod version (specific version deployed) :
https://script.google.com/a/mydomain.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- GET response
- V8 Disabled :
- GET response
doGet()
:- Dev version (latest code deployed) :
https://script.google.com/a/mydomain.com/macros/s/{Dev-Deployment-ID}/dev
- Prod version (specific version deployed) :
https://script.google.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- Called client-side :
google.script.run
- Dev version (latest code deployed) :
https://script.google.com/a/mydomain.com/macros/s/{Prod-Deployment-ID}/exec
- Prod version (specific version deployed) :
https://script.google.com/a/mydomain.com/macros/s/{Prod-Deployment-ID}/exec
- Dev version (latest code deployed) :
- GET response
- V8 Enabled :
- Script Editor :
- Intended behaviour of the webapp, depending on the
flag
value :flag === value1
: Typically, first time run :- Frontend : Apps settings form is disabled.
- Backend :
- Any submitted data will be rejected (i.e.
setSettings()
won't modify any properties). appinit()
execution is allowed
- Any submitted data will be rejected (i.e.
flag === value2
: Web app already intialized/configured :- Frontend : Apps settings form is enabled
- Backend :
- Any form data submitted by the user will be applied.
appinit()
execution is not allowed, unless an "App Reset" is explicitly requested.
- I'm kind of resisting the idea of using templated HTML outputs, maybe because I don't want the code to be closely tied to Google Apps Script's specific concepts and "ways of doing things", and so keep it clean and "easily convertible" to other backend languages.
- Exploring possible combinations, as far as allowed by the V8 runtime of the Google Apps Script framework⁽¹⁾⁽²⁾, of asynchronous execution and exception handling for both backend and frontend functions in order to address the above scenario.
- Finished importing entries from the old worklog.
- Some
README.md
refinements.
- Exploring : On page/webapp URL load, a backend flag value would be checked first. Depending on the value of this flag, the app would decide whether to load the form to view/update the settings (i.e. app already initialized), or call
appinit()
backend function (i.e. first time run). - Things to consider : As previously tested, loading dynamic HTML content into the frontend seems to require backend functions to return HTML code through
HtmlService.createTemplateFromFile()
(i.e. templated HTML) instead ofHtmlService.createHtmlOutput()
.
-
Relying on a
GET
parameter value,WebAppURL?reset=true
for instance, is not safe because the URL is kept with it's parameters, so if the page gets reloaded the application will be reset again!function doGet(e) { var userProperties = PropertiesService.getUserProperties(); if ( (userProperties.getProperty('INIT_ALREADY_RUN') !== 'YES') || (e.parameter.reset === 'true') ) { Logger.log(appinit()); return HtmlService.createHtmlOutputFromFile('index') .setTitle('Gmail AutoResponder - Settings'); } else { return HtmlService.createHtmlOutputFromFile('index') .setTitle('Gmail AutoResponder - Settings'); } }
-
Odd behaviour of Google Apps Script for G-Suite accounts (first noticed on May 06, and thouroughly investigated on May 10):
- When Chrome V8 runtime is enabled,
ScriptApp.getService().getUrl()
returns :https://script.google.com/macros/s/{Deployment-ID}/(exec|dev)
. - When Chrome V8 runtime is disabled,
ScriptApp.getService().getUrl()
returns :https://script.google.com/a/mydomain.com/macros/s/{deployment-id}/(exec|dev)
.
- When Chrome V8 runtime is enabled,
-
Tried Templated HTML with scriplets to "simulate" a page reload after
resetApp()
function is run : it is not possible to load a URL, dynamically provided by the scriplet<?= ScriptApp.getService().getUrl() ?>
and usingwindow.open()
, into the active window, unless you override the recommended default behaviour of Google Apps Script, which protects against clickjacking by setting theX-Frame-Options
HTTP header. -
Using
window.write()
, overwrite index page with its same code returned, as a HTML templated code, by a backend function : page content seems to load without issues exceptCKEditor
.
- Exploring ways to implement a "first time run" process to initialize the webapp, without having to go/redirect to a custom URL.
ScriptApp.getService().getUrl()
doesn't seem to return the correct URL in case of a G-Suite account :https://script.google.com/a/mydomain.com/macros/s/AKfy-----------------------k9/dev
: missinga/mydomain.com
betweenhttps://script.google.com/
and/macros/s/AKfy-----------------------k9/dev
.- Imported a few old Worklog entries.
-
So I forgot that script properties accept only string values, and any other type would be converted, including boolean. Consequently, a
if
statement didn't work as expected sincetrue
andfalse
were evaluated as literal non empty strings that are both equivalent to the boolean valuetrue
. Adjusted code accordingly. -
Adjusted project's scopes :
https://www.googleapis.com/auth/drive
, instead ofhttps://www.googleapis.com/auth/drive.readonly
. -
Now all app settings are configurable from the web frontend.
-
Deployed, intialized and run the application successfully with both G-Suite and free Google accounts.
-
Updated
worklog.md
andREADME.md
.
TIME_INTERVAL
hardcoded to10
. because :- this value has proved to be reliable,
TIME_INTERVAL
should be equal to the parameter initially given to theeveryMinutes()
method when script triggers were created, which there is no way to change afterwards other than deleting and recreating these triggers.
- No need now for spreadsheet templates.
Filters
andLogs
are generated and initialized byappinit()
. - Frontend and backend code for "getting" and "setting"
NO_REPLY
andSTAR_PROCESSED_MESSAGE
properties (more tests are needed). - Started enhancing the processing of properties values of
setProperties()
parameters object (set default values...etc). - User can either initialize or reset app settings using
appinit()
. - Deleted non needed files from the repository.
2020-05-03 (code)
- Added to my reading list : Google Apps Script: Demystifying Time Zones in Apps Script - Part 2
- Significant progress working on AppInit code.
2020-05-02 (code)
- How to tell if the application is run for the first time? The only way that comes to my mind is checking whether there is a user script property,
'alreadyRun'
for example, of which the value ∉ {0, false, undefined, null, NaN, ""}. - Started
appinit()
function draft.
- App Init script roles : First time / run once script executed after the user has been authenticated (i.e. has granted access to his Google account) and the frontend URL has been visited (i.e.
doGet()
function is run) for the first time :- Create
Filters
andLogs
spreadsheets. Get URLs to show next to each one's input field. - Place all app's files into the same Drive folder. Get and show URL of the folder.
- Create and set user script properties to their default values.
- [Create triggers [, set Enable/Disable App flag] ].
- Let the user manually enable the application.
- Load app parameters into the frontend and let the user modify and save them.
- Create
- Time Zone :
-
I couldn't understand what reference time zone Apps Script uses when processing Date/Time data (
Date
objects, for instance). It was neither Script's nor Calendar's in my tests! It's not the time zone of the OS on which the browser is running, and not even that of the location I'm connecting from! It's just like Google would automagically guess your time zone! So why would I bother setting it in the first place?! And to make it more confusing, there can be different time zones under the same Google account, depending on the programmatical context: Calendar time zone, Script time zone, Spreadsheet's time zone...! -
So I guess I will just settle with this solution, as stated by a StackOverflow user :
“If there are multiple users of the script in different time zones, then I set the Time Zone in the script to +GMT 00:00 no daylight savings. And leave it at that.” – 🌎
-
And as a precautionary measure, I'll keep the DST offset parameter.
-
-
I've been testing the
options
parameter of the methodsendEmail(recipient, subject, body, options)
, which behaves the same asGmailThread.reply(body, options)
, with the following code :GmailApp.sendEmail('name@domain.com', 'Apps Script : Test message', 'This is a test messages from APps Script', { cc: null, bcc: null, noReply: null });
-
Tried a few combinations of values for
cc
,bcc
andnoReply
properties, using both free and G-Suite Google accounts, and it seemed that it is safe to always default tonull
. -
In addition, these methods check whether given email addresses (recipient, Cc and Bcc) are valid and throws the exception
Invalid email
if they're not. So there is no need to provide backend code to validate addresses. -
Updated code accordingly.
-
I couldn't keep the same work pace in the last couple of days due to some preoccupations. So I'm just trying to keep my daily commitment..
-
Telling whether it is a G-Suite or a Free account : I checked two online posts that I had added to
raw_notes.md
on March 28. One of them suggests a method that doesn't seem to work any longer. The other suggests a seemingly working solution relying on the Admin SDK Directory Service that needs to be enabled from the Script Editor UI throughResources->Advanced Google Services...
, which I'm trying to avoid at the moment (unless it's required by other app features and/or makes things easier). -
But, at the end, it seems that we can simply check whether script user's email address ends with
gmail.com
or some custom domain name! So I guess I'll settle with this for the moment :userProperties.setProperty('ISENABLED_NOREPLY', (Session.getActiveUser().getEmail().split('@')[1]!=='gmail.com')?true:false);
2020-04-28 (code)
- Finished customizing CKEditor for form's text area :
- Set a default response message body in the backend. Trying to set a placeholder text with CKEditor 4 on the frontend, and the Configuration Helper (
confighelper
) plugin seems needed. The configuration isn't straightforward, as almost all resources I could find online suppose that a custom configuration fileconfig.js
along with a directory containing needed plugins are used, i.e. available for any customization, which doesn't apply to our case. - Considering the desired customizations, I'm trying to figure out whether or not it's worth/easier to use CKEditor 5 instead.
- Finally, I managed to create a basic example by checking the source code of this blog post by
confighelper
plugin author.
- Set a default response message body in the backend. Trying to set a placeholder text with CKEditor 4 on the frontend, and the Configuration Helper (
2020-04-27 (code)
- I would prefer using CKEditor, as a plain JavaScript solution, over TinyMCE which is developed in TypeScript (and I have no plans to learn TypeScript at the moment).
- According to most resources I checked, CKEditor creates its own DOM when
replace()
method is called, right at the end of page load, to replace form'stextarea
element. So, I had to force CKeditor to update the text area value using theupdateElement()
method before the form content is submitted to the backend. In addition, I had to use CKEditor'ssetData()
method, instead of setting element'sinnerText
property value, in order to update the text area with the content retrieved from the backend. - Customizing CKEditor toolbar..
- Removed two directories from this repository :
draft_code/client-to-server
: content moved to another generic repository I'm working on, to be adapted and published as a code snippet.app/frontend
: as the Material Design Lite library, on which this frontend template is based, will no longer receive further development/updates.
2020-04-26 (code)
- Tried a basic CKEditor 4 preset for message body text area, but the content is not sent to the backend on form submit. Falled back to plain HTML
textarea
which worked fine, for both retrieving and modifying settings. I'll try a basic TinyMCE setup and see. - Worked on another code repository.
-
Since it is not possible to change script time zone from within a Script app, I'm trying to figure out a way for providing a "script user-side" time zone parameter that can be modified using the frontend, and leave script's time zone at "GMT+00".
-
A Google user isn't given a default profile picture if he has never set one manually. So, the app should provide an alternate/default picture in case
getPhotoUrl()
returnsnull
. -
Added a default user profile picture encoded in base64
-
It would make sense to retrieve app's settings on page load if a "enable/disable app" switch is used. There seem to be two choices for an app status switch :
- A boolean script user property to be set accordingly.
- Function(s) to create and delete triggers.
-
raw_notes.md
revision :- Deleted old non-needed entries.
- Moved some "TODO" entries to
TODO.md
.
-
Custom errors to be created for / thrown by
setProperties()
function :- Provided Spreadsheets IDs are not valid, either because the resources do not exist or are not readable/writable by the script user.
- Invalid start/finish hours
- Invalid execution time interval
- Invalid Cc email address.
- "Reply with 'noreply' address" is set for non G-Suite user.
- Message body exceeds maximum number of characters. Format/Content not allowed.
- Enhanced code across the repository :
Code.js
: get all parameters from script user properties, otherwise assign default values.- Replaced
LOG_SS_ID
withLOGS_SS_ID
. frontend_index.html
:- Added link to revoke script's access to user data (logout).
- Added default values next to input fields
- Refined/Updated
README.md
andworklog.md
.
2020-04-23 (code)
-
Basic frontend example can now "set" Apps' settings.
-
A couple of backend functions now use objects as parameters, instead of arrays. Consequently, there was one less function needed which was removed.
-
Added some basic styling to highlight data retrieved from backend app.
-
All frontend functions moved to
gmail-autoresponder.js
.
-
Got a basic file upload example to work properly. (Code)
-
First working frontend example that retrieves App settings from backend. Code :
frontend_index.html
,frontend.js
-
Refined a little bit
worklog.md
.
2020-04-21 (code)
-
For some reason, files created on Drive from Blob data (= input file of a submitted form) lose their MIME type and get corrupted. What I couldn't understand is that up until the file is uploaded to the server, and right before a Drive file is created with its data by calling
DriveApp.createFile(blob)
, the blob type is correct. The backend functionprocessForm()
ofdraft_code\client-to-server\Code.js
was modified to illustrate the issue : -
So basically, some fairly reliable resources and accepted solutions on the web suggest to first process the submitted file with
FileReader()
, and pass it as a data URL to a backend function for a second process that extracts content type from it, decodes the submitted base64 data, and callsUtilities.newBlob()
to create a new blob object forDriveApp.createFile(blob)
. -
Here is a basic draft code as a wrap-up of what I've understood so far from the examples I studied. For the time being, this focuses only on that content type issue. Further development is needed to process forms with multiple types of input data (not only file upload) :
-
Frontend (client) :
/* // Example 1 function sendFileToDrive(file) { var reader = new FileReader(); reader.onload = function (event) { var content = reader.result; google.script.run.withSuccessHandler(updateProgressbar) .uploadFileToDrive(content, file.name); } reader.readAsDataURL(file); } */ // Example 2 function sendFileToDrive(file) { var reader = new FileReader(); reader.onloadend = function (event) { google.script.run.withSuccessHandler(updateProgressbar) .uploadFileToDrive(event.target.result, file.name); } reader.readAsDataURL(file); } // On form submit function FileUpload() { var allFiles = document.getElementById('myFile').files; sendFileToDrive(allFiles[0]); // Since there is only 1 file }
-
Backend (Google Apps Script):
/* // Example 1 function uploadFileToDrive(base64Data, fileName) { var contentType = base64Data.substring(5, base64Data.indexOf(';')); var bytes = Utilities.base64Decode(base64Data.substr(base64Data.indexOf('base64,') + 7)); var blob = Utilities.newBlob(bytes, contentType, fileName); var driveFile = DriveApp.createFile(blob); } */ // Example 2 function uploadFileToDrive(base64Data, fileName) { var splitBase = base64Data.split(','); var contentType = splitBase[0].split(';')[0].replace('data:', ''); var bytes = Utilities.base64Decode(splitBase[1]); var blob = Utilities.newBlob(bytes, contentType); blob.setName(fileName); var driveFile = DriveApp.createFile(blob); }
-
2020-04-20 (code)
-
Objective : Provide a same web page / frontend to both get current app's parameters' values (= prefill form fields on page load using
google.script.run
to call server-side "getters") and update them on submit. -
Researching and testing code about Google Apps Script Client-to-Server Communication
-
There are still concepts that I'm trying to deeply understand how they imply or impact each other, namely script scopes, APIs' scopes, whether or not it is required to connect to a GCP project, deploying as "a web app" vs "API Executable"... For instance, I had to publish the app as "API Executable" to be able to run through the Apps Script API some initialization functions (providing 'Logs' and 'Filters' spreadsheets' IDs...etc). But now, as I'm working on a frontend, I have to publish the app as "a web app" to issue client-to-server calls and provide a convenient way to show and update app's configs. So I guess, I will just make my best to both learn and enhance my code as I go.
But hey, I normally “code” more than 1 hour a day! C'mon, it's not that bad!
- Working on a basic HTML file that will let us "get" and/or "set" app's parameters.
- Information to retrieve/modify :
- User's profile picture
- User's
nameemail - Time zone
- Message body (HTML)
- Script user's parameters :
FILTERS_SS_ID
LOGS_SS_ID
START_HOUR
FINISH_HOUR
TIME_INTERVAL
DST_OFFSET
- I had to add another scope
https://www.googleapis.com/auth/drive.readonly
to retrieve the URL of script user's photo
- Information to retrieve/modify :
Significant update of README.md
's structure and content (draft).
Started adding old worklog entries translated from French.
Still refining README.md
, along with some modifications here and there.
- Cleaning up
README.md
-
clasp
limitations:-
There seems to be a long standing open issue about CI pipelines and services integration.
-
As I was trying to find a way to automate, or bypass altogether, the process described in
clasp run
CLI doc, I came to the conclusion that there is no automated way to associate a Google Apps Script project to a Standard GCP Project :-
[…]
2. Deploy the Script as an API executable (Easiest done via GUI at the moment).
—Source 🌎 -
Setup Instructions :
[…]
3. Set theprojectId
to your Apps Script project :
[…]
- In the menu, clickResources > Cloud Platform project...
- PasteProject number
inChange Project
and clickSet Project
.
—Soucre 🌎 -
General procedure for using the Apps Script API to execute Apps Script functions :
[...]
Step 2: Set up the common Cloud Platform project : Both your script and the calling application need to share the same Cloud Platform (GCP) project. This GCP project can be an existing project or a new project created for this purpose. Once you have a GCP project, you must switch your script project to use it.
—Soucre 🌎-
Switching to a different standard GCP project :
[…]
(4). In the Apps Script editor, open the script whose GCP project you want to replace.
(5). Click Resources > Cloud Platform project.
(6). In the Change Project section, paste the project number you copied into the text field, then click Set Project.
(7). A warning screen explains the effects of changing the Cloud Platform project. Read the notice carefully, and click Confirm.
—Source 🌎
-
-
The GCP project must be a standard GCP project; default projects created for Apps Script projects are insufficient.
—Soucre 🌎-
When standard GCP projects are required :
[…]
- When you have an application that needs to execute functions in your script project using the Apps Script API's scripts.run method.
—Source 🌎
-
-
-
-
gcloud
limitations:
Conslusion : Usinggcloud
would make some sense only if Google Cloud SDK is already installed and actively used by the developer wanting to deploy this Apps Script project.- Exploring
gcloud
capabilities. Trying to automate/script the "GCP Project" part of the Apps Script application setup process. - Apparently, there is no possibility to create credentials for a GCP project through
gcloud
CLI, which is really frustrating : - Many online resources suggest the use of Identity-Aware Proxy (IAP) service to programmatically create OAuth clients :
- Exploring
- I had to use
Fiddler
to intercept sequences of HTTP requests to Drive API URLs while usinggdrive
. Trying to mimic these requests usingCURL
. - After days of researching, I finally managed to find
CURL
command lines corresponding to each of the following Drive operations :- [Not Curl] Construct Access Token request URL; Get the code to use in the next step
- Request Access Token / Authorization Code
- Request a new Authorization Code using the refresh token
- Upload files to Drive; Import XLSX file as a Google SpreadSheet
- Create Drive Directory
- Move a Drive file to another folder
- Other useful operations :
- Getting information about the authenticated Google user
- Rename a file in Drive
CURL
will be used instead ofgdrive
from now on.- I will probably document what I have found about dealing with Google Drive API using
curl
in dedicated MD file/Repo/Gist/Blog post. - So, there is no need now to use Google Apps Script code in order to upload/import files to Drive. Gotta update code accordingly.
- Exploring alternative ways :
- To make [continuous] deployment of the application as automated as possible.
- To make the initial setup of the application easy, with one function call.
- To upload/import files to Drive using just the API, without relying on third-party tools like
gdrive
.
- Restructuring/Cleaning code.
Drafting instructions for project setup using both clasp
and gdrive
. Deployed successfully an updated version of the code.
2020-03-29, 03-31 (code)
Exploring clasp
tool for automated deployment of Google Apps Script project. Successfully deployed a first version of the code.
2020-03-28 : First commit during COVID-19 national lockdown 😷 (code)
Documenting: Collecting notes about app logic, features and auto-deployment
Added new examples of AppScript execution errors (from summary).
Added first draft of README.md
.
2018-09-21, 11-20 (code)
Added and updated sample frontend code using Material Design Lite.
Entries to be translated from the old worklog :
Original :
Revue du code source de l’application web, après plus d’un an d’exécution continue en production, avec plus de 6700 réponses automatiques envoyées.
Liste exhaustive des types d'erreurs reportées par Google Apps Scripts (résumés en provenance de l'adresse
apps-scripts-notifications@google.com
) durant l'année, illustré chacun par un exemple. Informations à prendre en considération dans les prochaines améliorations du code:
Start Function Error Message Trigger End 10/07/2018 20:06 autoReply Limit Exceeded: Email Body Size. (line 99, file "Code") time-based 10/07/2018 20:06 10/04/2018 20:43 autoReply Document {DOCUMENT-ID-DELETED} is missing (perhaps it was deleted, or you don't have read access?) (line 22, file "Code") time-based 10/04/2018 20:44 9/17/18 12:53 AM autoReply Service error: Spreadsheets (line 63, file "Code") time-based 9/17/18 12:53 AM 7/26/18 11:16 PM autoReply Gmail operation not allowed. (line 62, file "Code") time-based 7/26/18 11:16 PM 4/24/18 4:52 AM autoReply Service timed out: Spreadsheets (line 63, file "Code") time-based 4/24/18 4:53 AM 3/23/18 10:46 PM autoReply We're sorry, a server error occurred. Please wait a bit and try again. time-based 3/23/18 10:46 PM 1/25/18 8:22 PM autoReply We're sorry, a server error occurred. Please wait a bit and try again. (line 125, file "Code") time-based 1/25/18 8:24 PM 12/01/2017 08:45 archiveLog Sorry, it is not possible to delete all non-frozen rows. (line 26, file "Archive_Log") time-based 12/01/2017 08:45 10/02/2017 20:58 autoReply Argument too large: subject (line 97, file "Code") time-based 10/02/2017 20:58 10/01/2017 08:02 archiveLog You do not have permissions to access the requested document. (line 11, file "Archive_Log") time-based 10/01/2017 08:02 8/30/17 11:06 PM autoReply Invalid email: Judith Pin <> (line 92, file "Code") time-based 8/30/17 11:06 PM
2017-12-11 (code)
Original :
Modification des codes source afin de rectifier un problème empêchant la réinitialisation mensuelle de la feuille du journal des sessions d’exécutions.
Original :
En vérifiant les journaux des messages traités ainsi que les occurrences d’exécution de la session du 13/11/2017 : la session s’est déroulée correctement après les dernières mises à jours des codes source.
2017-11-13 (code)
Original :
Les sessions d’exécution du 11/11/2017 et le 12/11/2017 du programme de réponses mail automatiques du compte OPERATIONS se sont correctement déroulées après la dernière mise à jour du code source. Les changements ont été généralisés sur les autres programmes des comptes OPERATIONS2, OPERATIONS3, OPERATIONS4, OPERATIONS5 et OPERATIONS6. Par ailleurs, des fonctions pour effacer mensuellement le journal des occurrences de chaque session d’exécution des programmes ont été ajoutées à leurs codes source respectifs.
Original :
Fin de la nouvelle version du code source. Premier déploiement pour le compte
OPERATIONS
. Le code sera au fur et à mesure amélioré selon les résultats.
Original :
Continuation du développement du code amélioré du programme des réponses mail automatiques.
Original :
Continuation du développement et test des premières améliorations du code source pour une meilleure performance d’exécution.
Original :
Début d’optimisation du code source pour une meilleure performance d’exécution : Mise à jour du code envisagés : Au lieu d’extraire, à chaque exécution, tous les identifiants des messages traités durant tout le mois depuis le journal des opérations pour vérifier si un message n’a pas été déjà traité, le programme vérifierait juste les identifiants des messages traités dans la dernière occurrence qui seraient déjà mis en cache.
Original :
Vérification des programmes (journaux et configurations) :
- L’archivage des journaux du mois d’octobre a été correctement exécuté pour toutes les instances du programme.
- Adresses ajoutées à la liste d’exclusion
From
de chacun des documents de configuration.
Original :
Les codes sources des programmes de réponses mail automatiques ont été mis à jour suite au changement de l’heure locale qui a eu lieu le 29/10/2017.
Original :
Les deux premières sessions d’exécution des programmes de réponses mail automatiques associés aux comptes OPERATIONS5 et OPERATIONS6 ont été respectivement exécuté le 21/10/2017 et le 22/10/2017. 4 réponses automatiques ont été envoyées, 9 messages reçus sautés. Les adresses expéditrices avec la mention
do-not-reply
ont été ajoutées à la liste d’exclusion. Les résultats des sessions OPERATIONS5 et OPERATIONS6 seront suivis durant toute la semaine afin de corriger toute éventuelle anomalie. N.B. : Depuis l’exécution de la première session de réponse mail automatique le 23/08/2017, 1203 réponses ont été envoyées.
Original :
Configurations des programmes de réponses mail automatiques pour les comptes
OPERATIONS5 <operations5@mycompany.com>
etOPERATIONS6 <operations6@mycompany.com>
. Les premières sessions seront exécutées le jour même à partir de 20:00 (heure locale).
Original :
Etudes, rectification et suggestion d’amélioration suite aux remarques formulées dans le rapport du 03/10/2017:
Rectification du document de configuration du programme de
OPERATIONS2
auquel une opération d’archivage a été appliquée par erreur ; ce qui causait le traitement de l’intégralité des messages reçu sans aucun filtrage.Une amélioration du code est à envisager suite aux erreurs reportées par le service
Google Apps Script
:
- Les messages d’erreur
Argument too large: subject (line 97, file "Code") et Limit Exceeded: Email Body Size. (line 97, file "Code")
indiquent que le corps du message de réponse composé du texte informatif principal et de l’historique de la conversation peut potentiellement dépasser la limite de la taille maximale du corps de message de réponse.- Le concept permettant de contourner ce problème peut être résumé comme suit :
- L’ensemble du message (texte informatif + historique de la conversation) sera initialement stocké dans une chaîne de caractère (String).
- Si la taille de la chaîne dépasse 20Ko l’excédent sera supprimé et remplacé par des points de suspension.
Original :
Retour sur les résultats des sessions d’exécution des programmes associés aux comptes
OPERATIONS
,OPERATIONS2
,OPERATIONS3
etOPERATIONS4
entre le 22/09/2017 et 02/10/2017.
- Premières remarques :
- Aucun filtrage n’a été appliqué aux messages reçus durant la session du 01/10/2017 et le 02/10/2017 du programme de
OPERATIONS2
. Une réponse automatique a été envoyée pour chaque message détecté et traité.- La nécessité d’ajouter une colonne au journal contenant l’éventuelle raison d’exclusion d’un message se confirme.
- La couleur de remplissage des lignes, distinguant les messages sautés des réponses automatiques envoyée, n’a pas été correctement appliquée entre le 24/09/2017 et le 30/09/2017 aux journaux de
OPERATIONS
etOPERATIONS2
. Il ne peut s’agir que d’un bug/disfonctionnement du programme.
Original :
Configuration du programme de réponses mail automatique pour un quatrième compte Google:
OPERATIONS4 <operations4@mycompany.com>
.
Original :
Evaluation des résultats des sessions d’exécution du 20/09/2017 et 21/09/2017: 23 réponses envoyées.
2017-09-20 (code)
Original :
- Evaluation des résultats des sessions d’exécution du 19/09/2017: 26 réponses envoyées :
- Mise à jour du code: Extension de deux minutes de l’intervalle de recherche des derniers emails reçus sur chacune des trois boîtes emails à chaque itération du programme afin de ne pas rater les emails coïncidant avec l’instant d’exécution.
2017-09-19 (code)
Original :
- Evaluation des résultats des sessions d’exécution du 18/09/2017: 19 réponses automatiques envoyées :
OPERATIONS
: 60 itérations du programme ayant récupérés 68threads
. 68 messages traités, dont 50 sautés et 18 réponses automatiques envoyées.- OPERATIONS2 :
- 60 itérations du programme ayant récupérés 38
threads
. 38 messages traités et sautés.- 1 message non traité:
- L’heure de réception a coïncidé avec le déclenchement de la deuxième itération du programme pendant la session d’exécution du 18/09/2017. La partie du code recherchant et récupérant les derniers mails reçus l’aurait, par conséquent, raté.
- Le script du compte
OPERATIONS3
a détecté et sauté le message, comme il est configuré pour exclure les messages à destination deOPERATIONS
etOPERATIONS2
(adresses respectives ajoutées à la colonneTO_BLACKLIST
du document de configurationAutorespond-config
de l'instance de l'application associée au compte GoogleOPERATIONS3
).- Le script du compte
OPERATIONS
n’a pas détecté le message vu que l’itération qu’il l’aurait traité (exécutée à 19:26:02 GMT) a détecté un message plus récent dans la même conversation et auquel une réponse a été en effet envoyée.- De toute façon, le message a été traité peu après par l’équipe
OPERATIONS2
même.- OPERATIONS3 : 60 itérations du programme ayant récupérés 25
threads
. 25 messages traités, dont 24 sautés et une réponse envoyée.- Ajout du fichier
Autorespond-config-OPS3.xlsx
au code source:
- Un deuxième modèle du fichier
Autorespond-config
a été ajouté au code source, illustrant -à titre d'exemple- la configuration utilisée pour l'application associée au compteOPERATIONS3
, l'empêchant d'envoyer une réponse à un message reçu si celui-ci est aussi destiné au moins à l'une des adressesoperations@mycompany.com
etoperations2@mycompany.com
et serait donc traité par l'une des applications respectives leur étant associées.- A cet égard, les 3 instances en exécution sont en effet configurées comme suit:
- OPERATIONS : Traite tous les messages répondant aux critères de filtrage préconfigurés, excluant ainsi:
- les accusés de lectures
- les messages d'administration système (
postmaster
,mailer-daemon
)- les messages en provenance des adresses mail de la société (
*@mycompany.*
)- les messages en provenance des adresses avec l'alias
noreply/no-reply
.- les messages en provenance des adresses ajoutées au fur et à mesure à la liste d'exclusion
FROM_REGEX_BLACKLIST
- les messages à destinations anonymes
undisclosed-recipients
.- OPERATIONS2 : en plus des critères de filtrage précités, elle ne traite pas les messages destinés aussi à
operations@mycompany.com
.- OPERATIONS3 : en plus des critères de filtrage précités, elle ne traite pas les messages destinés aussi au moins à l'une des adresses
operations@mycompany.com
etoperations2@mycompany.com
.- Une même approche sera adoptée pour les autres instances de l'application qui seraient ultérieurement ajoutées et associées à d'autres compte Google.
2017-09-18 (code)
Original :
Evaluation des résultats des sessions d’exécution du 16/09/2017 et 17/09/2017 : 31 réponses automatiques envoyées :
OPERATIONS
:
- 120 itérations du programme ayant récupérés 84
threads
. 84 messages traités, dont 63 sautées et 21 réponses automatiques envoyées.OPERATIONS2
:
- 120 itérations du programme ayant récupérés 53
threads
. 53 messages traités, dont 51 sautées et 2 réponses automatiques envoyées.OPERATIONS3
:
- 120 itérations du programme ayant récupérés 95
threads
. 95 messages traités, dont 88 sautées et 7 réponses automatiques envoyées.- Améliorations et mises à jour :
- Les codes source ont été mis à jour pour activer le suivi (= ajouter
une étoile
au message sur le client webmailGmail
) de chaque message traité.
2017-09-16 (code)
Original :
Evaluation des résultats des sessions d’exécution du 14/09/2017 et 15/09/2017.
- Améliorations et mises à jour :
- Ajout d'une nouvelle adresse aux listes d’exclusions respectives
From :
de chaque compte (i.e.OPERATIONS
,OPERATIONS2
,OPERATIONS3
).- Modification du code pour la mise en copie normale
Cc
(au lieu deCci
) des adresses d’administration pour un meilleur filtrage et suivi des réponses automatiques envoyées (i.e. pour une meilleure lisibilité sur l’application webGmail
avec des libellés personnalisés, par exemple). L'adresse mise en copie est en effet un alias deamine@mycompany.com
; Dans le cas des réponses automatiques programmées jusqu’au 16/09/2017, les alias sont respectivement : it-operations@mycompany.com, it-operations2@mycompany.com, it-operations3@mycompany.com.OPERATIONS
2 etOPERATIONS3
: A partir de la session d’exécution du 16/09/2017, le système de filtrage des messages reçus par destination vérifiera les champsCc :
etCci :
en plus du champTo :
.- Prévisions : Comme un identifiant unique est attribué à chaque version d’un même message envoyé à plusieurs destinataires de
*@mycompany.*
, il va falloir penser à un autre critère de filtrage de tels messages pour qu’ils ne soient pas traités plusieurs fois. L’identifiantMessage-ID
, selon les spécifications du document « RFC 822 » de l’IETF, répond le plus aux critères requis. Une expression régulière pour l’extraction de cet identifiant a été développée et préparée pour utilisation dans de prochaines versions du programme :^Message-ID:\s*[<A-Za-z0-9!#$%&'*+-/=?^_`{}|~.@]*
- Révision (2019-06-09): Il est possible d'extraire l'en-tête
Message-ID
sans avoir à utiliser une expression régulière sur tout le contenu du message original, et ce en utilisant la méthodegetHeader(name)
de la classeGmailMessage
.
Original :
Evaluation des résultats de la session d’exécution du 13/09/2017 : 60 itérations correctes du programme ayant récupérés 33
threads
. 33 messages traités, dont 19 sautées et 14 réponses automatiques envoyées. Déploiement de versions adaptées du programme pour les deux comptesOPERATIONS2
etOPERATIONS3
.
Original :
Evaluation des résultats de la session d’exécution du 12/09/2017 : 60 itérations correctes du programme ayant récupérés 53
threads
. 53 messages traités, dont 42 sautées et 11 réponses automatiques envoyées.
Original :
Evaluation des résultats de la session d’exécution du 11/09/2017 : 60 itérations correctes du programme ayant récupérés 66
threads
. 66 messages traités, dont 45 sautées et 21 réponses automatiques envoyées. Un message récupéré depuis la boîte emailoperations@OldMailServer.com
n’a pas été traité. L’hypothèse établie dans le rapport du 09/09/2017 se tient.
- Révision (2019-06-08) : Le script de test initial aurait été créé pour vérifier si l'application avait déjà traité des messages envoyés au compte non-Google
operations@OldMailServer.com
et récupérés sur la boîte Gmail G-Suiteoperations@mycompany.com
, et ce en vérifiant les IDs des threads (auxquels ces messages sont respectivement attribués par Gmail), retournés par la requête de recherche, contre ceux des messages/threads traités journalisés dans le document Google SpreadsheetAutorespond-log
. Néanmoins, l'analyse du 12/09/2017 porte ici à confusion, et comme les résulats du 11/09/2017 ne sont plus vérifiables, un nouveau script de test (voir code) a été développé afin de confirmer que ces threads sont traités d'une façon normale, c.à.d. que l'ID Gmail (qui est différent duMessage-ID
de la normeRFC 822
) du thread correspond à l'ID de son premier message. A noter que ces messages risquent quand même de ne pas être relevés par l'application à cause des éventuels retards mis à leur récupération.
Original :
Evaluation des résultats des sessions d’exécution du 09/09/2017 et 10/09/2017 : 73 messages traités, dont 51 sautées et 22 réponses automatiques envoyées.
Original :
Analyse des résultats de la session d’exécution du 08/09/2017 :
- 58 messages traités, dont 44 sautées pour des raisons valides et 14 réponses envoyées.
- 2 messages non traités :
- MESSAGE (1): L’heure de réception du message était très proche (en amont) de l’instant de l’itération du programme qui a eu lieu exactement à 19:06 (GMT).
- MESSAGE (2): Le message a été reçu sur la boîte email
operations@OldMailServer.com
et récupéré sur la boîte principaleoperations@mycompany.com
.
- Rédaction en cours d'un script de test (voir code) afin de vérifier si de tels messages auraient compromis un traitement pertinent des messages: Récupération des IDs des
threads
auxquels appartiennent les messages envoyés [exclusivement] à l’adresseoperations@OldMailServer.com
. Vérifier les valeurs récupérées contre les journaux des messages traités.
Original :
Evaluation des résultats de la session d’exécution du 07/09/2017 : 36 messages traités, dont 27 sautées pour des raisons valides et 9 réponses envoyées.
2017-09-07 (code)
Original :
Analyse des résultats de la session d’exécution du 06/09/2017 :
- Le projet Google Apps Script
AutoRespond
est configuré avec un déclencheur en fonctions du temps qui se lance après chaque 10 minute. Les heures des premières et dernières exécutions effectives du programme sont respectivement 19:06 (GMT) et 04:56 (GMT). En effet, le programme s’exécute continument après chaque 10 minute durant toutes les 24 heures. Cependant le traitement des messages reçus n’est effectué que si la condition est satisfaite, à savoir : être dans la plage horaire [20h-06h].- Comme les messages Gmail sont organisés sous forme de threads (conversations), le programme récupère à chaque exécution les nouveaux
threads
ainsi que ceux mis à jour (c.à.d. réception de nouvelles réponses à une ancienne conversation (échange)) dans les dernières 10 minutes et traite ensuite leurs derniers messages respectifs.- Pour chaque
thread
, si le dernier message ne répond à aucun critère d’exclusion, une réponse automatique lui serait envoyée ; sinon, le programme passe authread
suivant. Dans chacun des deux cas, les messages traités ainsi que le nombre dethreads
récupérés à chaque exécution sont journalisés. Les informations du journal (log) permettent le suivi, l’analyse et le diagnostic des éventuels problèmes rencontrés.- Deux cas de figures se sont présentés suite à la dernière session d’exécution; qui peuvent nécessiter une analyse de comportement du programme, une évaluation des risques ainsi que des éventuelles améliorations du code source :
- MESSAGE (1): Le message a été reçu vers 21:16 mais n’a pas été traité.
- Vu qu’il était le dernier message de son
thread
jusqu’à la fin de la session d’exécution, la seule cause apparente serait le fait que sa réception a coïncidé avec le déclenchement du programme et il n’a pas été récupéré parmi les messages reçus dans les dernières 10 minutes.- Le message n’a pas été non plus récupéré dans l’itération suivante.
- Il serait donc plus prudent d’ajouter une marge d’erreur à l’intervalle de temps. Ce serait pratique d’attribuer une valeur dynamique à cet intervalle, soit par exemple 1.5x la durée séparant deux exécutions (automatique) consécutives du programme. Documentation en cours sur les moyens disponibles pour y parvenir.
- MESSAGE (2): Deux minutes après, et dans le même
thread
, le message a été suivi par un autre (i.e. une réponse) envoyé par (accounting@mycompany.com
) avec l’adresse (operations@mycompany.com
) en copie.
- Vu que le dernier message du
thread
venait d’une adresse mail exclue, il a été sauté.- Le présent cas laisse à penser à d’autres cas probables pouvant être critique, comme celui d’un message répondant à tous les critères de réponse automatique suivi juste après, dans le même
thread
, d’un mail exclu mais sans qu’il soit envoyé depuis une autre adresse email deMyCompany
; cas d’un accusé de lecture d’un ancien message dans le mêmethread
reçu du même expéditeur, par exemple.- Etude en cours d’améliorations et méthodes alternatives pour traiter avec plus de prudence les files des derniers messages reçus.
Original :
Analyse des résultats de la session d’exécution du 05/09/2017 :
- 57 messages traités : 18 réponses envoyées, 39 messages sautés pour des raisons valides.
- Les réponses automatiques envoyées ont couverts tous les
threads
Gmail reçus dans la plage horaire [20h-06h].- Mises à jour mineures du code source.
- Documentation sur les améliorations envisagées :
- Utilisation du Cache : pour le stockage temporaire et la consultation rapide des identifiants des derniers messages traités, au lieu d’extraire et de rechercher dans toutes les valeurs de la colonne D
Message ID
de la première feuille du documentGoogle Spreadsheet
de journalisationAutorespond-log
.- Etude d’une nouvelle architecture
Master/Slave
du programme pour déclencher simultanément les réponses automatiques de plusieurs comptes depuis un même script asynchrone avec des requêtesHTTP POST
.
Original :
Évaluation de l’exécution du programme du compte
OPERATIONS
entre le 31/08/2017 (soir) et le 05/09/2017 (matinée):
- 162 threads détectés, dont 114 sautés et 48 réponses envoyées.
- Ajout d'adresse mails à la liste d’exclusion.
Original :
Evaluation de l’exécution de la session du 31/07/2017 :
- 50 mails traités, dont 37 sautés.
- Ajoutée des adresses à la liste d’exclusion
- Un message
SPAM
sans une vraie adresseenvelop sender
niFrom :
a déclenché une erreur, puisque la méthodeGmailMessage.getFrom()
dans le code a retourné la valeurJudith Pin <>
qui n’est pas une adresse valide pour envoyer une réponse avec la méthodeGmailThread.reply(body, options)
. Bien que l’erreur n’était pas bloquante et ait été bien reportée par email, il serait plus judicieux de journaliser de tels cas avec le message d’erreur comme note. Cette suggestion peut faire objet d’une future amélioration du code avec des éventuels traitements d’exceptions.- Idées et prévisions : Exécution du programme sur toutes les boîtes emails depuis un même programme central :
- La méthode la plus sûre serait de communiquer, depuis le script
operations
, avec d’autres scriptsGoogle Apps Script
associés aux autres comptes, en utilisant des requêtes HTTP POST sur des connexions chiffrées (SSL/TLS).- Comme prérequis, il va falloir d’abord publier chacun des autres scripts en tant qu’application web (avec d’éventuelles restrictions d’accès pour protéger les données).
- Les autres scripts auraient les droits suivant :
- Modification du document
Google Spreadsheet
:Autorespond-log
, propriété du compteoperations@mycompany.com
.- Lecture depuis le document
Google Spreadsheet
:Autorespond-config
, propriété du compteoperations@mycompany.com
.- Consultation en cours des documentations ainsi que les forums d’aide et support officiels aux sujets précités.
2017-08-30 (code)
Original :
Confirmation de l’exécution optimale de la session du 29/07/2017 du programme associé au compte Google
operations@mycompany.com
. Le test d’archivage du journal des messages traités s’est exécuté comme planifié et avec succès. Dorénavant, au début de chaque mois, l’historique des opérations du mois précédent sera archivé dans une feuille séparée du même fichier. Etude en cours de la possibilité de gérer les réponses automatiques de toutes les boîtes emails de la société avec un seul programme lisant les configurations depuis un même fichier et enregistrant toutes les opérations exécutées dans le même journal. Si cela s’avère faisable, une révision considérable du tout le code source serait nécessaire.
Original :
Confirmation de l’exécution optimale de la session du 28/07/2017 du programme associé au compte Google
operations@mycompany.com
avec les dernières mises à jours du code.
- Améliorations :
- Inclusion du code HTML constituant le corps du message de réponse dans un fichier HTML du même projet
Google Apps Script
, au lieu de l’importer depuis un fichier externe hébergé sur un espace d’hébergement web tiers.- Rajout d’un fichier de script supplémentaire au même projet et sa programmation pour exécution automatique mensuelle. Le script archivera les opérations (i.e. réponses envoyées, et messages sautés) de chaque mois dans une nouvelle feuille du document log
Spreadsheet
Autorespond-log
afin d’alléger la feuille principale du journal.- Le code source sera publié le 30/08/2017
- Configuration provisoire pour test et validation : le script
Archive_log.gs
a été paramétré pour exécution automatique mensuelle le 30 de chaque mois ; en l’occurrence, une première exécution aurait lieu le lendemain 30/08/2017.
Original :
Evaluation des performances du programme associé au compte
operations@mycompany.com
durant son exécution du 26/08/2017 au 28/08/2017 :
- 87 messages traités:
- 16 réponses automatiques envoyées, dont 5 étaient non pertinentes (adresses email à ajouter à la liste d'exclusion)
- 71 messages sautés pour des raisons valides.
- Améliorations du code :
- Ajout d’une nouvelle feuille dans le fichier de journalisation
Autorespond-log
pour l’enregistrement de l’heure d’exécution ainsi que le nombre de conversations (threads) Gmail (récupérés et traités) du dernier intervalle de temps (prédéfini, et après lequel le programme se ré-exécute).- Marquage de la fin de session d’exécution globale (20h-06h) sur chacune des feuilles du journal afin d’en faciliter la lecture et l’analyse.
- Le code source sera publié le 30/08/2017
Original :
Evaluation des performances du programme associé au compte
operations@mycompany.com
durant son exécution entre 25/08/2017 à 20:00 et 26/08/2017 à 06:00 :
- 32/33 des messages reçus dans la plage horaire 20h-06h ont été traités.
- 1 message non détecté. Il s’agit d’un deuxième accusé de lecture du même message par le même destinataire. Vu que les deux emails font partie de la même conversation (thread) Gmail, seul le plus récent a été traité.
- 3 réponses automatiques envoyées
- 29 messages exclus pour des raisons valides
- Vu le résultat assez satisfaisant de son exécution, le code source -en sa dernière version- sera retenu.
2017-08-25 (code)
Original :
Bilan de l’exécution du programme pour le compte
operations@mycompany.com
pour le 24/08/2017 :
- 17 réponses automatiques envoyées entre 20:28 et 06:35 (heure locale)
- Amélioration du programme :
- Réorganisation des lignes de déclaration des variables pour une meilleure lisibilité et portabilité du code.
- Enregistrement des configurations sur une seule feuille du document
Autorespond-config
avec plusieurs colonnes, au lieu de plusieurs feuilles contenant chacune un filtre. L'ancienne version fichier a été archivée sous le nomAutorespond-config_OLD-till-2017-08-24
. Adaptation du code.- Rajout d’une valeur de décalage pour faciliter l’ajustement de la plage horaire d’exécution en cas de changement de l’heure locale.
- Utilisation d’une adresse générique
no-reply
afin de dissuader les destinataires de répondre directement aux messages automatiques. Par ailleurs, cela nous épargnera de configurer et maintenir sur chaque installation du logicielOutlook
un filtre pour en supprimer les copies reçues.- Exclusion des adresses email contenant les mots
noreply
etno-reply
.- Journalisation de tous les emails, traités et sautés.
Original :
Bilan de l’exécution du programme pour le compte
operations@mycompany.com
:
- 24 réponses automatiques envoyées entre 21:06 et 06:26 (heure locale).
- La stratégie adoptée par les services et applications Google pour déterminer l’heure exacte des événements (l’heure de réception des messages en l’occurrence) porte plutôt à confusion. Par conséquent, comme Google affirme qu’elle utilise l’heure UTC sur ses services en ligne, une plage horaire plus large sera utilisée pour que l’intervalle 20h-06h (heure locale) soit toujours couvert malgré les éventuels changements (i.e. début, suspension et fin de l’heure d’été). La ligne de code suivante :
if (((hour < 6) || (hour >= 20)) && ((threads = GmailApp.search('is:inbox after:' + timeFrom)).length !== 0)) {
sera donc modifié en :if (((hour < 6) || (hour >= 19)) && ((threads = GmailApp.search('is:inbox after:' + timeFrom)).length !== 0)) {
.- Confirmation avec l'équipe des Opérations de la liste des contacts à exclure de la réponse automatique.
- Désinscription de quelques newsletters via les liens fournis dans les corps de leurs messages respectifs.
- Modification du message de réponse automatique : l’adresse de modération
amine@mycompany.com
enCci
au lieu deCc
.- Groupement des fichiers du programme dans un même dossier sous
Google Drive
. Partage avec le compte Google deAMINE
(lecture et modification) pour faciliter (centraliser) les consultations et les mises à jour.
2017-08-23 (code)
Original :
Fin du développement de la deuxième version (améliorée).
- Tests appliqués et réussis :
- Exclusion des conversations Gmail (avec de nouveau message) au-delà des dernières 10 minutes
- Exclusions des messages envoyés depuis des adresses avec les alias
MAILER-DAEMON@*
etpostmaster@*
- Exclusion des messages en provenance des adresses emails de
MyCompany
(domaine principal + tous les domaines alias)- Exclusion des accusés de lecture
- Exclusion des messages avec des destinations anonymes (
undisclosed-recipients
)- Exclusion des messages déjà traités (i.e. journalisés sur le fichier
Autorespond-log
)
Original :
Correction et amélioration du code :
- Résolu : Interprétation comme expression régulière des chaînes de caractères extraites des fichiers de configuration.
- Tests, adaptations et corrections.
Original :
Problèmes en cours de traitement :
- Le contenu extrait des cellules de la feuille
From_regex_blacklist
du documentGoogle Spreadsheet
Autorespond-config
, par la fonctionMatchesRegex()
ne semble pas être correctement interprété comme étant une expression régulière contre laquelle l’expéditeur devrait être vérifié afin d’exclure les adresses email deMyCompany
ainsi que les adresses emails d’administrateur système réservéespostmaster@...
etmailer-daemon@...
.- La fonction
ContainsString()
appliquée sur les en-têtes de l’email (i.e. message brut/original) ne détecte pas la présence des expressionsreport-type=disposition-notification
etreport-type=delivery-status
, ce qui aurait permis l’exclusion des accusés de lecture et des rapports de remise.
2017-08-18 (code)
Original :
- Définitions complètes des fonctions d’extractions et de vérification de valeurs depuis des documents
Google Spreadsheet
(configurations et journauxLogs
). Le fichier des configurationAutorespond-config
contient les feuilles suivantes:To_whitelist
,To_regex_whitelist
,To_blacklist
,To_regex_blacklist
,From_whitelist
,From_regex_whitelist
,From_blacklist
,From_regex_blacklist
,msgHeaders_blacklist
,msgHeaders_regex_blacklist
.- Un modèle du fichier
Autorespond-config
sera ultérieurement ajouté au code source sous le format XLSX.
- Test et débogage du code.
2017-08-17 (code)
Original :
Optimisation du code :
- Améliorations apportées ou en cours de développement :
- Lecture de configurations depuis un document
Google Spreadsheets
(Autorespond-config
),- Enregistrement (journalisation) et vérification des informations identifiant les messages traités dans/depuis un document
Google Spreadsheets
(Autorespond-log
),- Définition de fonctions génériques pour vérifier les données des en-têtes des messages contre les données extraites des documents précités,
- Récupération du message de réponse automatique (corps HTML) depuis un emplacement sécurisé sur l’espace d’hébergement web de la société.
- Utilisation d’une combinaison de configurations pour filtrer les messages à traiter,
Original :
Test et évaluation de la lecture et écriture de données sur des documents
Google Spreadsheet
, pour la journalisation des opérations et la lecture de configurations.
Original :
Améliorations du code : Premiers essais et évaluation d’une journalisation des opérations vers des documents
Google Spreadsheet
, proprité du même compte Google exécutant le script.
Original :
La solution finalement retenue et implémentée pour le stockage et l’importation du contenu du corps de message de réponse est l’hébergement d’un fichier HTML sur notre espace web, sous un répertoire protégé par nom d’utilisateur et un mot de passe. Tests et validation.
Original :
Problématique : Inclusion d’un texte unique dans le corps du message de réponse sans aucune mention d’informations de contact au format texte. La solution envisagée et d’inclure un tableau de contacts sous format Image dans le corps du message. Améliorations étudiées :
- Inclusion du corps de message au format HTML depuis un fichier externe :
- Cas d’un fichier texte au format HTML stocké sur
Google Drive
. Difficulté : Aucune procédure simple et fonctionnelle n’a été trouvée pour lire le contenu brut d’un fichier texte stocké surGoogle Drive
.- Cas d’un document
Google Docs
exporté au format HTML. Difficulté : Il était possible de récupérer le contenu d’un documentGoogle Docs
sous forme de code HTML et l’insérer dans le corps du message de réponse, mais l’image est par défaut bloquée par la plupart des clients de messagerie modernes puisqu’elle est hébergée dans un emplacement externe.- Cas d’un fichier texte au format HTML récupéré via un URL : Un fichier contenant le contenu du corps du message au format HTML a été stocké sur notre espace d’hébergement web, et récupéré via l’URL
http://mycompany.com/email_body.html
. L’image présentant le tableau des contacts y a été codée en Base64 . Difficulté : Le corps du message généré dépasse ainsi la taille maximale autorisée pour un script/projetGoogle Apps Script
.
Original :
Optimisation du code source du script associé à la boîte email
operations@mycompany.com
:
- Exclusion des messages (souvent spam) dont la destination est anonyme (
undisclosed-recipients
)- Exclusion des messages automatiques envoyés depuis des administrateurs de serveurs de messagerie (
mailer-daemon
,postmaster
)- Exclusion des accusés de lecture et des rapports de remise.
Original :
Coordination avec l’équipe des opérations et discussions à propos de la meilleure stratégie à adopter pour la programmation des messages de réponse automatique envisagée hors les heures de travail.
Original :
- Véirication des résultats de la première exécution programmée entre 06:00GMT et 20:00GMT.
- Idées pour amélioration:
- Exclusion des accusés de lecture. Il va falloir interpréter en-avale les en-têtes dans le code source (en-têtes) de chaque email traité afin de vérifier si le contenu MIME
multipart/report
est de type :report-type=disposition-notification
.- Précautions pour assurer une exécution continue du programme jusqu’à la fin de la plage horaire prédéfinie. Cela dépend de plusieurs facteurs :
- Le temps d’exécution maximal autorisé durant une journée (24h). Les références en ligne à ce sujet (documentation Google officielle comprise) laissent des ambiguïtés : ce serait entre 1h et 6h. Par conséquent, l’intervalle de temps entre chaque exécution du script devra être convenablement choisi selon le temps moyen nécessaire pour le traitement des derniers messages reçus sur le compte mail.
- Le fuseau horaire et l’heure d’été. Afin d’éviter toute confusion, Google utilise à la base l’heure UTC sur ses plateformes et services, y compris Google Apps Script. Par conséquent, et suite aux changements de l’heure locale, il serait difficile d’inclure des référence horaires dynamiques dans le code source du script ou bien de les modifier manuellement à chaque changement. A concevoir donc, éventuellement, une solution pour adapter automatiquement la plage horaire. En attendant, une plage horaire plus inclusive ; 19h-6h (GMT), soit 20h-7h (GMT+1), sera utilisée.
- Voir la possibilité de vérifier l’authenticité des expéditeurs (signatures des e-mails…)
- Externaliser les filtres et les contenus personnalisés pour une meilleure portabilité du code.
2017-07-28 (code)
Original :
- Fin de développement de la première version du script.
- Première exécution (automatique) de test pour la boîte email
operations@mycompany.com
prévue entre 28/07/2017, 20:00GMT et 29/07/2017 06:00GMT.
2017-07-27 (code)
XXXXXXXX
- Specifications and requirements :
- The script will be configured for steady auto-execution between 8pm and 6am, under every Google user account.
Original :
Continuation de l’étude et développement.
- Spécifications et cahier de charges :
- Le script sera configuré pour exécution automatique régulière entre 20h et 6h sur chaque compte utilisateur Google.
- Il vérifiera les derniers messages reçu. Puisque les messages sont interprétés par Gmail comme des groupes de > discussion (threads), les discussions avec de nouveaux messages (réponses ou transferts) seront inclues.
- Les messages en provenance des contacts de
MyCompany
seront exclus.- Option : Les messages/discussions avec le libellé Gmail
_autoRep
seront considérés comme déjà traités et seront > donc systématiquement exclus.- Traitement des messages : Envoie du corp de la réponse automatique sous forme de texte riche (HTML) suivie des > informations (date, expéditeur, destinataires, objet) et une citation du contenu du message traité.
- Attribution du libellé Gmail
_autoRep
pour marquer le message comme traité.
2017-07-26 (code)
Developing a first prototype of a script to send automatic responses to emails received in a specific timeframe of each day.