Personalizations field error when sending multiple dynamic template emails in sendgrid - python

I'm trying to send a bulk email using Sendgrid Dynamic Templates in Django and getting this error: The personalizations field is required and must have at least one personalization.
Using sendgrid 6.9.7
Does anyone see where I might be going wrong:
from django.conf import settings
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, To
def send_mass_email():
to_emails = [
To(email='email1#gmail.com',
dynamic_template_data={
"thing_i_want_personalized": 'hello email 1',
}),
To(email='email2#gmail.com',
dynamic_template_data={
"thing_i_want_personalized": 'hello email 2',
}),
]
msg = Mail(
from_email='notifications#mysite.com>',
)
msg.to_emails = to_emails
msg.is_multiple = True
msg.template_id = "d-template_id"
try:
sendgrid_client = SendGridAPIClient(settings.SENDGRID_API_KEY)
response = sendgrid_client.send(msg)
print(response.status_code)
print(response.body)
print(response.headers)
except Exception as e:
print(e)
print(e.body)
return
The output is
HTTP Error 400: Bad Request
b'{"errors":[{"message":"The personalizations field is required and must have at least one personalization.","field":"personalizations","help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#-Personalizations-Errors"}]}'

The Mail class does some stuff during initialization with the initial values (it sets private internal values based on to_emails), so you need to pass to_emails and is_multiple in the initializer and not later:
from django.conf import settings
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, To
def send_mass_email():
to_emails = [
To(email='email1#gmail.com',
dynamic_template_data={
"thing_i_want_personalized": 'hello email 1',
}),
To(email='email2#gmail.com',
dynamic_template_data={
"thing_i_want_personalized": 'hello email 2',
}),
]
msg = Mail(
from_email='notifications#mysite.com>',
to_emails = to_emails,
is_multiple = True
)
msg.template_id = "d-template_id"
try:
sendgrid_client = SendGridAPIClient(settings.SENDGRID_API_KEY)
response = sendgrid_client.send(msg)
print(response.status_code)
print(response.body)
print(response.headers)
except Exception as e:
print(e)
print(e.body)
return

Related

Choose Transactional Email

Full disclosure: I am very new to coding and I really don't know what I'm doing. Currently trying to send multiple transactional emails using SendinBlue SMTP API based on the one I pick. I have already made the templates on SendinBlue, but how do I call a specific template?
My code
from __future__ import print_function
import sib_api_v3_sdk
import smtplib
from sib_api_v3_sdk.rest import ApiException
from pprint import pprint
from email.mime.text import MIMEText
from flask import request, render_template
from sql_helper import *
# Configuration of API key authorization:
api_config = sib_api_v3_sdk.Configuration()
api_config.api_key['api-key'] = 'api_key'
#If Partnership Account:
#api_config = sib_api_v3_sdk.Configuration()
#api_config.api_key['partner-key'] = 'API_Key'
api_instance = sib_api_v3_sdk.TransactionalEmailsApi(sib_api_v3_sdk.ApiClient(api_config))
subject = "My Subject"
html_content = "<html><body><h1>This is my first transactional email </h1></body></html>"
sender = {"name":"company name","email":"admin#company.com"}
to = [{"email":"example#example.com","name":"Jane Doe"}]
# cc = [{"email":"example2#example2.com","name":"Janice Doe"}]
# bcc = [{"name":"John Doe","email":"example#example.com"}]
reply_to = {"email":"admin#company.com","name":"John Doe"}
headers = {"Some-Custom-Name":"unique-id-1234"}
params = {"parameter":"My param value","subject":"New Subject"}
send_smtp_email = sib_api_v3_sdk.SendSmtpEmail(to=to, reply_to=reply_to, headers=headers, html_content=html_content, sender=sender, subject=subject)
def send_email(template, recipient, custom_message = None):
if template == "approve":
template_id = 1
elif template == "deny":
template_id = 2
elif template == "skip":
template_id = 3
try:
#send transactional email
api_response = api_instance.send_transac_email(send_smtp_email)
pprint(api_response)
except ApiException as e:
print("Exception when calling SMTPApi -> send_transac_email: %s\n" % e)
Any help would be appreciated.
I really am not quite sure what I'm doing, so anything would help. I'm sorry if the code is bad, I'm just starting out.

How to send email in python with sendgrid and dynamic template, with BCC addresses?

In python 3 and sendgrid I need to send BCC type emails and use a dynamic template, which I built here
In the dynamic template I put a blank space to receive data that I will send. I created the variable {{lista}} in the blank space. It looked like this in the HTML of the whitespace template:
<div style="font-family: inherit; text-align: inherit">{{sentences}}</div>
My python code looked like this:
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Attachment, Mail
from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Personalization, Bcc
import os
API_KEY = "real id"
lista = ["Sentence 1 <br>", "Sentence 2 <br>"]
lista = {"sentences": lista}
recips = ['email1#gmail.com', 'email2#gmail.com', 'email2#gmail.com']
to_emails = [
To(email= 'one_valid_email#gmail.com',
dynamic_template_data = lista)]
personalization = Personalization()
personalization.add_to(To('one_valid_email#gmail.com'))
for bcc_addr in recips:
personalization.add_bcc(Bcc(bcc_addr))
message = Mail(
from_email=('emailsender#gmail.com'),
subject="email subject",
to_emails=to_emails,
is_multiple=True)
message.add_personalization(personalization)
message.template_id = 'real id'
try:
sendgrid_client = SendGridAPIClient(api_sendgrid)
response = sendgrid_client.send(message)
print(response.status_code)
#print(response.body)
#print(response.headers)
except Exception as e:
print(e.message)
return
The email is sent, but with empty templates, without "list" data
Please, does anyone know what could be wrong?
Here is the image of my template, the place I created the blank to receive the data:
And here the HTML code of the place, Edit Module HTML:
That is because you are trying to call the method add_personalization on the to_emails object, which is a list that you defined a few lines above:
to_emails = [To(email= 'one_valid_email#gmail.com']
You need to call add_personalization to the message object that you created, like this:
message.add_personalization(personalization)
Here's the full code with the fix:
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Attachment, Mail
from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException, Personalization, Bcc
import os
API_KEY = "real id"
lista = { "sentences": ["Sentence 1 <br>", "Sentence 2 <br>"] }
recips = ['email1#gmail.com', 'email2#gmail.com', 'email2#gmail.com']
to_emails = [To(email= 'one_valid_email#gmail.com']
personalization = Personalization()
personalization.add_to(To('one_valid_email#gmail.com'))
for bcc_addr in recips:
personalization.add_bcc(Bcc(bcc_addr))
message = Mail(
from_email=('emailsender#gmail.com'),
subject="email subject",
to_emails=to_emails,
is_multiple=True)
message.add_personalization(personalization)
message.dynamic_template_data = lista
message.template_id = 'real id'
try:
sendgrid_client = SendGridAPIClient(api_sendgrid)
response = sendgrid_client.send(message)
print(response.status_code)
#print(response.body)
#print(response.headers)
except Exception as e:
print(e.message)
return
Since your "sentences" dynamic template data is an array, you should likely loop through it too, so you can print each sentence. Try this in your template:
{{#each sentences}}
<div style="font-family: inherit; text-align: inherit">{{this}}</div>
{{/each}}

{ "message": "mismatching_state: CSRF Warning! State not equal in request and response." } following auth0 quickstart python 01-login - OAUTH

I implemented auth0 quickstart python 01-login with my Flask Application and am receiving this response:
{ "message": "mismatching_state: CSRF Warning! State not equal in request and response." }
Here is a snippet of that code logic:
from models import setup_db, Bay, db_drop_and_create_all
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from jinja2 import Environment, PackageLoader
from flask import Flask, render_template, request, Response, flash, redirect, url_for, jsonify, abort,session
from sqlalchemy import Column, String, Integer, create_engine,and_
from auth import AuthError, requires_auth
from functools import wraps
from os import environ as env
from werkzeug.exceptions import HTTPException
from dotenv import load_dotenv, find_dotenv
from authlib.integrations.flask_client import OAuth
from six.moves.urllib.parse import urlencode
import json
import constants
ENV_FILE = find_dotenv()
if ENV_FILE:
load_dotenv(ENV_FILE)
AUTH0_CALLBACK_URL = env.get(constants.AUTH0_CALLBACK_URL)
AUTH0_CLIENT_ID = env.get(constants.AUTH0_CLIENT_ID)
AUTH0_CLIENT_SECRET = env.get(constants.AUTH0_CLIENT_SECRET)
AUTH0_DOMAIN = env.get(constants.AUTH0_DOMAIN)
AUTH0_BASE_URL = 'https://' + AUTH0_DOMAIN
AUTH0_AUDIENCE = env.get(constants.AUTH0_AUDIENCE)
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__)
oauth = OAuth(app)
auth0 = oauth.register(
'auth0',
client_id=AUTH0_CLIENT_ID,
client_secret=AUTH0_CLIENT_SECRET,
api_base_url=AUTH0_BASE_URL,
access_token_url=AUTH0_BASE_URL + '/oauth/token',
authorize_url=AUTH0_BASE_URL + '/authorize',
client_kwargs={
'scope': 'openid profile email',
},
)
app.secret_key = constants.SECRET_KEY
app.debug = True
configedDB = setup_db(app)
if not configedDB:
abort(500)
#app.errorhandler(Exception)
def handle_auth_error(ex):
response = jsonify(message=str(ex))
response.status_code = (ex.code if isinstance(ex, HTTPException) else 500)
return response
'''
#TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs
'''
CORS(app, resources={r"/api/*": {"origins": "*"}})
'''
#TODO: Use the after_request decorator to set Access-Control-Allow
'''
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,true')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response
#----------------------------------------------------------------------------#
# Controllers.
#----------------------------------------------------------------------------#
#app.route('/')
def index():
return render_template('index.html')
#app.route('/callback')
def callback_handling():
#auth0 = app.config['auth0']
token = auth0.authorize_access_token()
resp = auth0.get('userinfo')
userinfo = resp.json()
print('>>>USER_INFO',userinfo)
session[constants.JWT_PAYLOAD] = userinfo
session[constants.PROFILE_KEY] = {
'user_id': userinfo['sub'],
'name': userinfo['name'],
'picture': userinfo['picture']
}
return redirect('/manager/bay/all')
#app.route('/login')
def login():
#auth0 = app.config['auth0']
#state = uuid.uuid4().hex
#redirect_url = url_for("auth.callback", _external=True)
#auth0.save_authorize_state(redirect_uri=redirect_url, state=state)
return auth0.authorize_redirect(redirect_uri=AUTH0_CALLBACK_URL, audience=AUTH0_AUDIENCE) #, state=state
#app.route('/logout')
def logout():
session.clear()
params = {'returnTo': url_for('home', _external=True), 'client_id': AUTH0_CLIENT_ID}
return redirect(auth0.api_base_url + '/v2/logout?' + urlencode(params))
Please note that for my .env file I have:
AUTH0_CLIENT_ID='l6yng5LFtKZIFZ6I56XXXXXXXX'
AUTH0_DOMAIN='double-helixx.us.auth0.com'
AUTH0_CLIENT_SECRET='egP3YRyAqtnHjtIkmnzWMPC0btJ3oN-odV001t9pgbKvc6nlT96PPrYsVeJzxTce'
AUTH0_CALLBACK_URL='http://127.0.0.1:5000/callback'
AUTH0_AUDIENCE='MYIMAGE'
Maybe this is the cause of the error from my side?
Python Samples Failing Out of the Box
authlib/issues
authlib-client-error-state-not-equal-in-request-and-response
It seems this is a recent issue for some people as well shown in here:
https://github.com/auth0-samples/auth0-python-web-app/issues/58
My auth0 Urls are:
I have an auth.py file as well that I have implemented- that may be the cause of the error?
import json
from flask import request, _request_ctx_stack
from functools import wraps
from jose import jwt
from urllib.request import urlopen
AUTH0_DOMAIN = 'double-helixx.us.auth0.com'
ALGORITHMS = ['RS256']
API_AUDIENCE = 'image'
## AuthError Exception
'''
AuthError Exception
A standardized way to communicate auth failure modes.
'''
class AuthError(Exception):
def __init__(self, error, status_code):
self.error = error
self.status_code = status_code
## Auth Header
'''
# implement get_token_auth_header() method
it should attempt to get the header from the request
it should raise an AuthError if no header is present
it should attempt to split bearer and the token
it should raise an AuthError if the header is malformed
return the token part of the header
'''
def get_token_auth_header():
"""Obtains the Access Token from the Authorization Header
"""
auth = request.headers.get('Authorization', None)
if not auth:
raise AuthError({
'code': 'authorization_header_missing',
'description': 'Authorization header is expected.'
}, 401)
parts = auth.split()
if parts[0].lower() != 'bearer':
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization header must start with "Bearer".'
}, 401)
elif len(parts) == 1:
raise AuthError({
'code': 'invalid_header',
'description': 'Token not found.'
}, 401)
elif len(parts) > 2:
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization header must be bearer token.'
}, 401)
token = parts[1]
return token
'''
# implement check_permissions(permission, payload) method
#INPUTS
permission: string permission (i.e. 'post:drink')
payload: decoded jwt payload
it should raise an AuthError if permissions are not included in the payload
!!NOTE check your RBAC settings in Auth0
it should raise an AuthError if the requested permission string is not in the payload permissions array
return true otherwise
'''
def check_permissions(permission, payload):
if 'permissions' not in payload:
raise AuthError({
'code': 'invalid_claims',
'description': 'Permissions not included in JWT.'
}, 400)
if permission not in payload['permissions']:
raise AuthError({
'code': 'unauthorized',
'description': 'Permission not found.'
}, 403)
return True
'''
# implement verify_decode_jwt(token) method
#INPUTS
token: a json web token (string)
it should be an Auth0 token with key id (kid)
it should verify the token using Auth0 /.well-known/jwks.json
it should decode the payload from the token
it should validate the claims
return the decoded payload
!!NOTE urlopen has a common certificate error described here: https://stackoverflow.com/questions/50236117/scraping-ssl-certificate-verify-failed-error-for-http-en-wikipedia-org
'''
def verify_decode_jwt(token):
jsonurl = urlopen(f'https://{AUTH0_DOMAIN}/.well-known/jwks.json')
jwks = json.loads(jsonurl.read())
unverified_header = jwt.get_unverified_header(token)
rsa_key = {}
if 'kid' not in unverified_header:
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization malformed.'
}, 401)
for key in jwks['keys']:
if key['kid'] == unverified_header['kid']:
rsa_key = {
'kty': key['kty'],
'kid': key['kid'],
'use': key['use'],
'n': key['n'],
'e': key['e']
}
if rsa_key:
try:
payload = jwt.decode(
token,
rsa_key,
algorithms=ALGORITHMS,
audience=API_AUDIENCE,
issuer='https://' + AUTH0_DOMAIN + '/'
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError({
'code': 'token_expired',
'description': 'Token expired.'
}, 401)
except jwt.JWTClaimsError:
raise AuthError({
'code': 'invalid_claims',
'description': 'Incorrect claims. Please, check the audience and issuer.'
}, 401)
except Exception:
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to parse authentication token.'
}, 400)
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to find the appropriate key.'
}, 400)
'''
#TODO implement #requires_auth(permission) decorator method
#INPUTS
permission: string permission (i.e. 'post:drink')
it should use the get_token_auth_header method to get the token
it should use the verify_decode_jwt method to decode the jwt
it should use the check_permissions method validate claims and check the requested permission
return the decorator which passes the decoded payload to the decorated method
'''
def requires_auth(permission=''):
def requires_auth_decorator(f):
#wraps(f)
def wrapper(*args, **kwargs):
token = get_token_auth_header()
payload = verify_decode_jwt(token)
check_permissions(permission, payload)
return f(payload, *args, **kwargs)
return wrapper
return requires_auth_decorator
Any ideas?

How to test an api that accesses an external service?

I am creating an application that receives some parameters and sends an email using a company server, when I do the test using postman it works, but using the same parameters to perform a test happens an error and I can't debug, only 500 error appears, can i see the test django server log?
urls.py
urlpatterns = [
path('modules/email/send', views_email.email_send.as_view(), name='email_send'),
]
views_email.py
class email_send(APIView):
permission_classes = (IsAuthenticated,)
def post(self, request): # ver anexo e como carregar os componentes
try:
body = json.loads(request.body.decode('utf-8'))
fromaddr = body.get("from")
to = body.get("to")
cc = body.get("cc")
subject = body.get("subject")
level = body.get("level")
level_color = body.get("level_color")
alert_name = body.get("alert_name")
body_html = body.get('body_html')
#Arquivos html
index_file = body.get("index_file")
header_file = body.get("header_file")
footer_file = body.get("footer_file")
# body_file = body.get("body_file")
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = fromaddr
message["To"] = to
message["CC"] = cc
....
part1 = MIMEText(text, "plain")
part2 = MIMEText(html_string, "html")
message.attach(part1)
message.attach(part2)
server = smtplib.SMTP('server.com.br', 25)
response = server.sendmail(fromaddr, toaddr, message.as_string())
if response == {}:
response = 200
else:
pass
return Response(response, content_type="text/plain", status=status.HTTP_200_OK)
except Exception as e:
return Response(str(e), content_type="text/plain", status=status.HTTP_400_BAD_REQUEST)
test_urls.py
from rest_framework.test import RequestsClient
from rest_framework.authtoken.models import Token
import requests
client = RequestsClient()
token = Token.objects.get(user__username='admin')
class EmailSendTest(TestCase):
def test_email_send(self):
headers = {
'Content-Type': "application/json",
'Authorization': "Token " + token.key
}
payload = {
"from": "#",
"to": "#",
"cc": "",
"subject": "Test views_email",
"alert_name": "Test views_email",
"level": "level",
"level_color": "default",
"index_file": "index.html",
"header_file": "header.html",
"footer_file": "footer.html",
"body_html": "Test"
}
response = client.post(
'http://testserver/motor-alertas/modules/email/send',
json=payload,
headers=headers,
)
self.assertEquals(response.content, 200)
I run the tests with the following command:
python3 -m pytest --cov=. && \
python3 -m coverage xml -i

Python Sendgrid add CC to email

I am using SendGrid for Python. I want to CC some people in an email. It seems like they may no longer support CC'ing on emails, though I'm not positive if that's true? But surely there is a work around to it somehow, but I am surprised I can't find much support on this.
Here is my basic code:
sg = sendgrid.SendGridAPIClient(apikey='*****')
from_email = Email(sender_address, sender_name)
to_email = Email(email_address)
subject = subject
content = Content("text/plain", email_message)
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())
How can I modify this so it will CC someone on an email?
Using the SendGrid's Personalization() or Email() class did not work for me. This is how I got it to work:
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Cc
# using a list of tuples for emails
# e.g. [('email1#example.com', 'email1#example.com'),('email2#example.com', 'email2#example.com')]
to_emails = []
for r in recipients:
to_emails.append((r, r))
# note the Cc class
cc_emails = []
for c in cc:
cc_emails.append(Cc(c, c))
message = Mail(
from_email=from_email,
to_emails=to_emails,
subject='My Subject',
html_content=f'<div>My HTML Email...</div>'
)
if cc_emails:
message.add_cc(cc_emails)
try:
sg = SendGridAPIClient(os.getenv('SENDGRID_API_KEY'))
sg.send(message)
except Exception as e:
print(f'{e}')
Hopefully this helps someone.
I resolved it. Santiago's answer got me mostly there, but here is what I needed to do:
sg = sendgrid.SendGridAPIClient(apikey='****')
from_email = Email(sender_address, sender_name)
to_email = Email(to_email)
cc_email = Email(cc_email)
p = Personalization()
p.add_to(to_email)
p.add_cc(cc_email)
subject = subject
content = Content("text/plain", email_message)
mail = Mail(from_email, subject, to_email, content)
mail.add_personalization(p)
response = sg.client.mail.send.post(request_body=mail.get())
If you don't include the p.add_to(to_email) it rejects it because there is no "to email" in the personalization object. Also, if you don't include the "to_email" inside the mail object it rejects it because it is looking for that argument, so you have to be a bit redundant and define it twice.
I've been looking at the code: https://github.com/sendgrid/sendgrid-python/blob/master/examples/mail/mail.py
And it looks like you can do that by adding a personalization to the mail, for example:
cc_email = Email(cc_address)
p = Personalization()
p.add_cc(cc_email)
mail.add_personalization(p)
Based on the answers here you can CC to email if you add another email to 'to_email'.
If you want to cc multiple user then in djanogo using sendgrid you need to import the below line
the function that will be used to send the mail
and finally how you ned to send the data paramters to the above function so that it can CC the person
email = send_sandgridmail(sender=sender,receiver=receivers,subject=subject,content=message,reply_to=sender,cc=[admin_mail_account_mail,"rawatanup918#gmail.com"],attachment=None)
i hope this'll help.simplified from #anurag image script
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import To,Mail,ReplyTo,Email,Cc
def send_sandgridmail (sender, receiver, subject, content, reply_to=None, cc=[], attachment=None) :
# content = convert_safe_text(content)
# to email = To(receiver)
message = Mail(
from_email=str(sender),
to_emails=receiver,
subject= str(subject),
html_content = content)
if reply_to:
message.reply_to= ReplyTo(reply_to)
if attachment:
message.add_attachment (attachment)
if len(cc):
cc_mail = []
for cc_person in cc:
cc_mail.append(Cc(cc_person, cc_person))
message.add_cc (cc_mail)
try:
SENDGRID_API_KEY = 'your sendgrid api key'
sg= SendGridAPIClient (SENDGRID_API_KEY)
response= sg.send(message)
print (response.status_code)
# print (response.body)
# print (response.headers)
except Exception as e:
print(e)
return response

Categories