Hey Snowflake, send me an email

Snowflake can send emails now —so let’s build a stored procedure that sends you the results of a query every morning — including if there’s a Holiday somewhere in the world that’s affecting your numbers.

Felipe Hoffa
Snowflake
Published in
6 min readNov 29, 2022

--

Image generated by AI

Snowflake can send emails now

To send an email notification with Snowflake, you now need only one SQL call:

call system$send_email(
'my_email_int',
'felipe@example.com, dan@example.com, rajiv@example.com',
'This is the subject',
'This is the body'
);

To set this up, read the docs (or the posts by Rajiv Gupta and Dan Galavan):

The goal of this post is to tie this functionality with a scheduler and some formatting code — so every morning from Monday to Friday we can get the results of a SQL query, including any upcoming Holidays. Like this one:

An email sent by Snowflake, with the upcoming Holidays

Step 1: Develop a stored procedure that formats and emails the result of any query

This is the code I wrote, while debugging my abilities to email a well formatted query result:

with pretty_email_results as procedure()
returns string
language python
packages = ('snowflake-snowpark-python', 'tabulate')
handler = 'x'
as
$$
def x(session):
printed = session.sql(
"select * from table(result_scan(last_query_id(-1)))"
).to_pandas().to_markdown()
session.call('system$send_email',
'my_email_int',
'felipe@example.com',
'Email Alert: Task A has finished.',
printed)
$$
call pretty_email_results();

Some interesting details in it:

  • Snowflake now has “Anonymous Procedures” that allow you to define and call them in one step. You don’t need create or replace first and call later — which is especially useful while debugging.
  • I chose to write this procedure in Python, to use the library tabulate. This library will format the results of a query, so they look pretty on our outgoing email.
  • The tabulate library is already provided by Anaconda in Snowflake, so we get to use it with the line packages = (‘tabulate’).
  • With result_scan(last_query_id(-1) Snowflake can access the results of the last query executed — which allows pretty_email_results() to email whatever results we got in the previous step.
  • Snowpark gives us the session object in our stored procedure, which we can use to execute the result scan with session.sql().
  • session.sql() gives us a Snowpark DataFrame, and calling .to_pandas() on it gets usPandas DataFrame. Pandas’ .to_markdown() then gets to call tabulate — and it will complain unless you not explicitly request this package with packages=(...).

Step 2: Move the interactive code to a stored procedure

Once your interactive debugging is done, you can move the code to a permanent stored procedure:

create or replace procedure email_last_results(send_to string, subject string)
returns string
language python
runtime_version=3.8
packages = ('snowflake-snowpark-python', 'tabulate')
handler = 'x'
execute as caller
as
$$
import snowflake

def x(session, send_to, subject):
try:
body = session.sql(
"select * from table(result_scan(last_query_id(-1))) limit 100"
).to_pandas().to_markdown()
except snowflake.snowpark.exceptions.SnowparkSQLException as e:
body = '%s\n%s' % (type(e), e)
session.call('system$send_email',
'my_email_int',
send_to,
subject,
body)
return 'email sent:\n%s' % body
$$;

This looks similar to the previous code, with some minor differences:

  • execute as caller is needed here so the stored procedure can find the query history of the user calling it.
  • runtime_version=3.8 needs to be explicit in a permanent stored procedure.
  • I added code to catch exceptions and send a pretty email anyways.
  • I moved the subject and recipients to the procedure arguments.
  • I added a limit 100 to prevent sending TB sized emails.

With this I’m ready to email the results of any query with a simple call:

call email_last_results('felipe@example.com', 'results from snowflake');

Step 3: Create a procedure that executes a query and emails the results

Now a simple SQL stored procedure can run a query and ask our Python procedure to email the results:

create or replace procedure run_query_and_email_it()
returns string
execute as caller
as
$$
begin
-- any query you'd like to run
select 'now' as what, to_char(current_timestamp) as value
union all
select 'upcoming', day || ' ' ||holiday
from table(fh_db.public.holidays('US', [year(current_date), year(current_date)+1]))
where day between current_date() and current_date()+60
;
-- call the stored procedure that formats and emails the results
call email_last_results('felipe@example.com', 'upcoming holidays');
return 'done';
end
$$
;

This is cool, and it’s just a SQL stored procedure. There’s nothing too fancy about it — other than the results.

Test it with:

call run_query_and_email_it();

Step 4: Schedule sending the email

Snowflake has a task scheduler ready for your requests:

create or replace task email_me_frequently
warehouse = 's'
schedule = 'using cron 0 8 * * 1-5 America/Los_Angeles'
as call run_query_and_email_it();

In this case I’m asking it to run the stored procedure every morning at 8am PST, but only Monday to Friday (1–5).

Some notes:

  • I asked it to use my warehouse called “s”. Snowflake also offers Serverless Tasks, but those don’t run Python stored procedures.
  • I had to modify my holidays() UDF by moving the Python .zip dependencies from a user stage to a named stage. When it lived inside my user stage the task threw the error Remote file ‘holidays.zip’ was not found.
  • Change the schedule to every1 minute if you need to debug.

Don’t forget to start it:

alter task email_me_frequently resume;

And to monitor its runs:

select *
from table(information_schema.task_history(
scheduled_time_range_start=>dateadd('hour',-48,current_timestamp()),
result_limit => 100));
Images generated by AI

Looking forward: Alerts

Don’t worry too much about building an alert system — Snowflake is already developing this (currently in private preview):

CREATE ALERT Warehouse_Credit_Usage_Alert
WAREHOUSE = my_warehouse
SCHEDULE = ‘USING CRON 0 7 * * *UTC’ // everyday at 7 am
IF (EXISTS (SELECT
Warehouse_name,
SUM(CREDITS_USED) AS credits
FROM snowflake.account_usage.warehouse_metering_history
// aggregate warehouse Credit_used for the past 24 hours
WHERE datediff(hour, start_time, CURRENT_TIMESTAMP ())<=24
GROUP BY 1
HAVING credits > 10
ORDER BY 2 DESC))
THEN system$send_email (
‘My_email_notification_integration’,
‘admin1@company,com, admin2@company.com’,
‘Email Alert: Excessive warehouse usage!’,
‘Warehouse usage exceeds 10 credits in the past 24 hours’
)

Want more?

I’m Felipe Hoffa, Data Cloud Advocate for Snowflake. Thanks for joining me on this adventure. You can follow me on Twitter and LinkedIn. And subscribe to reddit.com/r/snowflake for the most interesting Snowflake news.

Images generated by AI

--

--

Felipe Hoffa
Snowflake

Data Cloud Advocate at Snowflake ❄️. Originally from Chile, now in San Francisco and around the world. Previously at Google. Let’s talk data.