-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(pipeline) : Handle Brevo & Dora contact information
This commit introduces: - An "all DORA emails" table that combines unique emails ffrom structures and services. - An "import from Brevo" DAG that retrieves the contacted emails - A "process_dora_brevo_contacts" DAG that that: * gets the list of all the known Brevo users * finds out who the "new users" are and imports them to Brevo * sends a marketing campaign to those users That's it. Optionnally/later, we could: - make use of an "excluded contacts" table that is imported from Grist (refused any marketing communications) - handle the soft bounces/hard bounces after a timeout. https://help.brevo.com/hc/en-us/articles/209435165-What-is-a-soft-bounce-and-a-hard-bounce-in-email-
- Loading branch information
Showing
13 changed files
with
321 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# No need to add more airflow variables here, there is very little chance that those | ||
# ever change and this is not sensitive information. | ||
BREVO_CURRENT_CONTACTS_LIST_ID = 5 | ||
BREVO_ALL_CONTACTS_LIST_ID = 6 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import functools | ||
from contextlib import contextmanager | ||
|
||
from airflow.providers.postgres.hooks.postgres import PostgresHook | ||
|
||
|
||
@functools.cache | ||
def hook(): | ||
return PostgresHook(postgres_conn_id="pg") | ||
|
||
|
||
@contextmanager | ||
def connect_begin(): | ||
engine = hook().get_sqlalchemy_engine() | ||
with engine.connect() as conn: | ||
with conn.begin(): | ||
yield conn | ||
|
||
|
||
def create_schema(schema_name: str) -> None: | ||
with connect_begin as conn: | ||
conn.execute( | ||
f"""\ | ||
CREATE SCHEMA IF NOT EXISTS {schema_name}; | ||
GRANT USAGE ON SCHEMA {schema_name} TO PUBLIC; | ||
ALTER DEFAULT PRIVILEGES IN SCHEMA {schema_name} | ||
GRANT SELECT ON TABLES TO PUBLIC;""" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import logging | ||
|
||
import airflow | ||
import pendulum | ||
from airflow.operators import empty, python | ||
from dag_utils.virtualenvs import PYTHON_BIN_PATH | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
default_args = {} | ||
|
||
|
||
def _import_rgpd_contacts(): | ||
import pandas as pd | ||
from airflow.models import Variable | ||
from dag_utils import constants | ||
|
||
from dags.dag_utils import pg | ||
from data_inclusion.scripts.tasks import brevo | ||
|
||
brevo_client = brevo.BrevoClient(token=Variable.get("BREVO_API_KEY")) | ||
|
||
pg.create_schema("brevo") | ||
|
||
contacts_df = pd.DataFrame.from_records( | ||
brevo_client.list_contacts(constants.BREVO_ALL_CONTACTS_LIST_ID), | ||
columns=[ | ||
"email", | ||
"id", | ||
"emailBlacklisted", | ||
"smsBlacklisted", | ||
"createdAt", | ||
"modifiedAt", | ||
], | ||
) | ||
with pg.connect_begin() as conn: | ||
contacts_df.to_sql( | ||
"all_rgpd_dora_contacts", | ||
con=conn, | ||
schema="brevo", | ||
if_exists="replace", | ||
index=False, | ||
) | ||
|
||
|
||
with airflow.DAG( | ||
dag_id="import_brevo", | ||
start_date=pendulum.datetime(2023, 11, 1, tz="Europe/Paris"), | ||
default_args=default_args, | ||
schedule_interval="@daily", | ||
catchup=False, | ||
concurrency=1, | ||
) as dag: | ||
start = empty.EmptyOperator(task_id="start") | ||
end = empty.EmptyOperator(task_id="end") | ||
|
||
import_rgpd_contacts = python.ExternalPythonOperator( | ||
task_id="import_rgpd_contacts", | ||
python=str(PYTHON_BIN_PATH), | ||
python_callable=_import_rgpd_contacts, | ||
) | ||
|
||
(start >> import_rgpd_contacts >> end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import logging | ||
|
||
import airflow | ||
import pendulum | ||
from airflow.operators import empty, python | ||
from dag_utils.virtualenvs import PYTHON_BIN_PATH | ||
|
||
default_args = {} | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def _sync_new_contacts_to_brevo(): | ||
from airflow.models import Variable | ||
from dag_utils import constants | ||
|
||
from dags.dag_utils import pg | ||
from data_inclusion.scripts.tasks import brevo | ||
|
||
all_emails = [ | ||
row[0] | ||
for row in pg.hook().get_records( | ||
sql="SELECT courriel FROM dora.int_dora__contacts", | ||
) | ||
] | ||
|
||
brevo_contacts = [ | ||
row[0] | ||
for row in pg.hook().get_records( | ||
sql="SELECT email FROM brevo.all_rgpd_dora_contacts", | ||
) | ||
] | ||
|
||
brevo_client = brevo.BrevoClient(token=Variable.get("BREVO_API_KEY")) | ||
|
||
new_emails = sorted(set(all_emails) - set(brevo_contacts)) | ||
|
||
brevo_client.import_to_list(constants.BREVO_ALL_CONTACTS_LIST_ID, new_emails) | ||
brevo_client.empty_list(constants.BREVO_CURRENT_CONTACTS_LIST_ID) | ||
brevo_client.import_to_list(constants.BREVO_CURRENT_CONTACTS_LIST_ID, new_emails) | ||
|
||
|
||
def _send_rgpd_notice(): | ||
from airflow.models import Variable | ||
|
||
from dags.dag_utils import constants | ||
from data_inclusion.scripts.tasks import brevo | ||
|
||
brevo_client = brevo.BrevoClient(token=Variable.get("BREVO_API_KEY")) | ||
brevo_client.create_and_send_email_campaign( | ||
subject="[data·inclusion] Notification RGPD", | ||
template_id=2, | ||
to_list_id=constants.BREVO_CURRENT_CONTACTS_LIST_ID, | ||
tag="di-rgpd-notice", | ||
from_email="[email protected]", | ||
from_name="L'équipe data inclusion", | ||
reply_to="[email protected]", | ||
) | ||
|
||
|
||
with airflow.DAG( | ||
dag_id="dora_rgpd_notifications", | ||
description="Sends RGPD notifications to DORA users", | ||
start_date=pendulum.datetime(2023, 11, 1), | ||
default_args=default_args, | ||
schedule_interval="@monthly", | ||
catchup=False, | ||
) as dag: | ||
start = empty.EmptyOperator(task_id="start") | ||
end = empty.EmptyOperator(task_id="end") | ||
|
||
sync_new_contacts_to_brevo = python.ExternalPythonOperator( | ||
task_id="sync_new_contacts_to_brevo", | ||
python=str(PYTHON_BIN_PATH), | ||
python_callable=_sync_new_contacts_to_brevo, | ||
) | ||
|
||
send_rgpd_notice = python.ExternalPythonOperator( | ||
task_id="send_rgpd_notice", | ||
python=str(PYTHON_BIN_PATH), | ||
python_callable=_send_rgpd_notice, | ||
) | ||
|
||
(start >> sync_new_contacts_to_brevo >> send_rgpd_notice >> end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
pipeline/dbt/models/intermediate/sources/dora/int_dora__contacts.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
WITH structure_contacts AS ( | ||
SELECT courriel | ||
FROM {{ ref('stg_dora__structures') }} | ||
WHERE courriel IS NOT NULL | ||
), | ||
|
||
service_contacts AS ( | ||
SELECT courriel | ||
FROM {{ ref('stg_dora__services') }} | ||
WHERE courriel IS NOT NULL | ||
), | ||
|
||
final AS ( | ||
SELECT * FROM structure_contacts | ||
UNION | ||
SELECT * FROM service_contacts | ||
) | ||
|
||
SELECT * FROM final |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.