email is not sent using SendGrid/cloud composer - google-cloud-platform

I'm trying to send an email using SenDGrid but the DAG is stuck on running.
I did the following:
set the environment variable SENDGRID_MAIL_FROM as my email
set the environment variable SENDGRID_API_KEY as the api I've generated from Sendgrid after confirming my personal email (same as sender email).
No spam im my email inbox.
Nothing found in the Activity section on SendGrid page and nothing is sent.
Can someone maybe point out what am I doing wrong?
My code:
from airflow.models import (DAG, Variable)
import os
from airflow.operators.email import EmailOperator
from datetime import datetime,timedelta
default_args = {
'start_date': datetime(2020, 1, 1),
'owner': 'Airflow',
"email_on_failure" : False,
"email_on_retry" : False,
"emails" : ['my#myemail.com']
}
PROJECT_ID = os.environ.get("GCP_PROJECT_ID", "bigquery_default")
PROJECT_ID_GCP = os.environ.get("GCP_PROJECT_ID", "my_progect")
with DAG(
'retries_test',
schedule_interval=None,
catchup=False,
default_args=default_args
) as dag:
send_email_notification = EmailOperator(
task_id = "send_email_notification",
to = "test#sendgrid.com",
subject = "test",
html_content = "<h3>Hello</h3>"
)
send_email_notification

Related

How to schedule an email using twilio sendgrid in django?

I'm currently building an app which contains email sending to multiple users which i'm able to do but i want to add a functionality which schedule's an email, for instance I'm using sent_at method as you can see below:-
settings.py
EMAIL_FROM = 'EMAIL'
EMAIL_API_CLIENT ='XXXXXXXX'
views.py
import json
from sendgrid import SendGridAPIClient
from django.conf import settings
message = Mail(from_email=settings.EMAIL_FROM,
to_emails=selectedphone,
subject=subject,
html_content=editor)
message.extra_headers = {'X-SMTPAPI': json.dumps({'send_at':
FinalScheduleTime})}
sg = SendGridAPIClient(settings.EMAIL_API_CLIENT)
response = sg.send(message)
if response.status_code == 202:
emailstatus = "Accepted"
elif .....
else.....
I've also tried message.extra_headers = {'SendAt':FinalScheduleTime} but it's not working either.
Here the FinalScheduleTime is of the datetime object. The sendgrip api accepts the UNIX timestamp according to the documentation. You can check it here
Hence to convert your datetime object into unix time stamp, you can use the time module of python.
scheduleTime = int(time.mktime(FinalScheduleTime.timetuple()))
Also, replace the message.extra_headers with message.send_at.
Hence, your final code will look like:
import json
import time
from sendgrid import SendGridAPIClient
from django.conf import settings
message = Mail(from_email=settings.EMAIL_FROM,
to_emails=selectedphone,
subject=subject,
html_content=editor)
scheduleTime = int(time.mktime(FinalScheduleTime.timetuple()))
message.send_at = scheduleTime
sg = SendGridAPIClient(settings.EMAIL_API_CLIENT)
response = sg.send(message)
if response.status_code == 202:
emailstatus = "Accepted"
elif .....
else.....
This is an official blog by Twilio on Using Twilio SendGrid To Send Emails from Python Django Applications - https://www.twilio.com/blog/scheduled-emails-python-flask-twilio-sendgrid
Also here are, official docs

Django APscheduler prevent more workers running scheduled task

I use APScheduler in Django, on Windows IIS to run my background script. Problem is, taks gets run multiple times. If I run same program on my PC, it only runs once, but when I upload to windows server (which hosts my Django app) it runs more times. I guess it has some connection with the number of workers? Job is scheduled, but each time job task is done, it's like it runs random number of instances. First 1 time, then 2, then 10, then again 2. Even tho I have 'replace_existing=True, coalesce= True, misfire_grace_time = 1, max_instances = 1'
planer_zad.py
from apscheduler.schedulers.background import BackgroundScheduler
from blog.views import cron_mail_overdue
def start():
scheduler.add_job(cron_mail_overdue, "cron", hour=7, minute=14, day_of_week='mon-sun', id="task002", replace_existing=True, coalesce= True, misfire_grace_time = 10, max_instances = 1)
scheduler.start()
apps.py
from django.apps import AppConfig
class BlogConfig(AppConfig):
name = 'blog'
def ready(self):
#print('Starting Scheduler...')
from .planer import planer_zad
planer_zad.start()
For test I tried 'interval':
scheduler.add_job(cron_mail_overdue, "interval", minutes=1, id="task002", replace_existing=True, coalesce= True, misfire_grace_time = 10, max_instances = 1)
Tried:
scheduler = BackgroundScheduler({
'apscheduler.executors.default': {
'class': 'apscheduler.executors.pool:ThreadPoolExecutor',
'max_workers': '1'
},
'apscheduler.executors.processpool': {
'type': 'processpool',
'max_workers': '1'
},
'apscheduler.job_defaults.coalesce': 'True',
'apscheduler.job_defaults.max_instances': '1',
'apscheduler.timezone': 'UTC',
})
scheduler.add_job(cron_mail_overdue, "cron", hour=9, minute=3, second=00, day_of_week='mon-sun', id="task002", replace_existing=True, coalesce= True, misfire_grace_time = 10, max_instances = 1)
scheduler.start()
Does not work. Sometimes it runs only once, then 12 times.
Just test if the object already exists in ready() :
# django/myapp/apps.py
from django.apps import AppConfig
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'
def __init__(self, app_name, app_module):
super(BlogConfig, self).__init__(app_name, app_module)
self.planer_zad = None
def ready(self):
if os.environ.get('RUN_MAIN', None) != 'true':
return
if self.planer_zad is None:
background_scheduler = BackgroundScheduler()
background_scheduler.add_job(task1, CronTrigger.from_crontab('* * * * *')) # Every minutes (debug).
background_scheduler.start()
return background_scheduler
def task1(self):
print("cron task is working")
You can then call it later :
# api.py
from django.apps import apps
#router.get("/background-task")
def background_task(request):
"""
Run a background task.
"""
user = request.user
blog_config= apps.get_app_config('blog')
background_scheduler = blog_config.background_scheduler
return {"status": "Success", "True": str(background_scheduler)}

Python smtplib email doesn't send to External Addresses

I'm able to automate some emails internally using mime and smtplib, but for some reason, these emails fail to send to external addresses (outside of company's domain)
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email import Encoders
import smtplib
SERVER = 'mailrelay'
FROM = 'myemail#internaldomain.com'
TO = ['myemail#internaldomain.com','someemail#externaldomain.com']
body = 'This is a test'
msg = MIMEMultipart()
msg["To"] = ','.join(TO)
msg["From"] = FROM
msg["Subject"] = 'Automated Test Email'
msgText = MIMEText(body, 'html')
msg.attach(msgText)
message = msg.as_string()
server = smtplib.SMTP(SERVER)
server.sendmail(FROM,TO,message)
server.quit()
Produces this error:
SMTPRecipientsRefused: {'someemail#externaldomain.com': (550, '5.7.1 Unable to relay')}
Admin insists that relaying is enabled, and told me that sending emails to external domains using Powershell works, so it can't be a relay issue.
So now I'm stuck. If it's not the problem python is telling me it is, is my admin wrong or is something else going on?
Any ideas?
Looks like I just needed to authenticate in this case:
server.ehlo()
server.starttls()
server.ehlo
server.login('user', 'pass')

Convert Google Doc to PDF and send email with PDF attachment

I am attempting to convert a Google Doc to a PDF (using the Drive API) and then attaching the file to an email (using the Gmail API).
The script runs, coverts the Google Doc to a PDF, sends an email with the attachment, but the PDF attachment is blank / corrupt.
I suspect the issue is with line: msg.set_payload(fh.read())
The relevant documentation: set_payload and io.Bytes()
Any guidance is greatly appreciated.
import base64
import io
from apiclient.http import MediaIoBaseDownload
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
fileId = '1234'
content_type = 'application/pdf'
response = drive.files().export_media(fileId=fileId, mimeType=content_type)
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, response)
done = False
while done is False:
status, done = downloader.next_chunk()
logging.info("Download %d%%." % int(status.progress() * 100))
message = MIMEMultipart()
message['to'] = 'myemail#gmail.com'
message['from'] = 'myemail#gmail.com'
message['subject'] = 'test subject'
msg = MIMEText('test body')
message.attach(msg)
main_type, sub_type = content_type.split('/', 1)
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fh.read()) # i suspect the issue is here
msg.add_header('Content-Disposition', 'attachment', filename='an example file name.pdf')
message.attach(msg)
message_obj = {'raw': base64.urlsafe_b64encode(message.as_string())}
service.users().messages().send(userId="me", body=message_obj).execute()
How about this modification? I think that about the download from Google Drive, your script is correct. So I would like to propose to modify the script for sending an email with the attachment file.
I thought that msg.set_payload(fh.read()) is one of the modification points as you say. So the data retrieved by getvalue() was converted by email.encoders.encode_base64(). And also I modified message_obj.
Modified script:
Please modify as follows.
From:
msg = MIMEText('test body')
message.attach(msg)
main_type, sub_type = content_type.split('/', 1)
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fh.read()) # i suspect the issue is here
msg.add_header('Content-Disposition', 'attachment', filename='an example file name.pdf')
message.attach(msg)
message_obj = {'raw': base64.urlsafe_b64encode(message.as_string())}
service.users().messages().send(userId="me", body=message_obj).execute()
To:
from email import encoders # Please add this.
msg = MIMEText('test body')
message.attach(msg)
main_type, sub_type = content_type.split('/', 1)
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fh.getvalue()) # Modified
encoders.encode_base64(msg) # Added
msg.add_header('Content-Disposition', 'attachment', filename='an example file name.pdf')
message.attach(msg)
message_obj = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()} # Modified
service.users().messages().send(userId="me", body=message_obj).execute()
Note:
This modification supposes that you have already been able to send emails using Gmail API. If you cannot use Gmail API, please confirm the scopes and whether Gmail API is enabled at API console.
In my environment, I could confirm that the modified script worked. But if this didn't work in your environment, I apologize.

Is it possible to save the sent email into the sent items folder using python?

I want to sends an email but sent mail is empty.
how to send an email, and then put a copy of it in the "Sent" mail folder.
what can i do?
Yes, its possible.
Basicaly you need to create an MIME email and then send it throug smptlib and than save it on Sent with imaplib.
The official imaplib documentation.
More detailed examples of using imaplib.
Here is an example:
import time
import ssl
import imaplib
import smtplib
import email
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
class Mail:
def __init__(self):
# considering the same user and pass for smtp an imap
self.mail_user = 'youruser#yourdomain.com'
self.mail_pass = 'pass'
self.mail_host = 'mail.yourdomain'
def send_email(self, to, subject, body, path, attach):
message = MIMEMultipart()
message["From"] = self.mail_user
message["To"] = to
message["Subject"] = subject
message.attach(MIMEText(body, "plain"))
with open(path + attach, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
"attachment; filename= \"" + attach + "\"",
)
message.attach(part)
text = message.as_string()
context = ssl.create_default_context()
with smtplib.SMTP_SSL(self.mail_host, 465, context=context) as server:
result = server.login(self.mail_user, self.mail_pass)
server.sendmail(self.mail_user, to, text)
imap = imaplib.IMAP4_SSL(self.mail_host, 993)
imap.login(self.mail_user, self.mail_pass)
imap.append('INBOX.Sent', '\\Seen', imaplib.Time2Internaldate(time.time()), text.encode('utf8'))
imap.logout()
if __name__ == '__main__':
m = Mail()
m.send_email('someone#somewhere.com', 'Hello', 'Its just a test!', 'c:\\', 'test.pdf')