Last modified: Oct 10, 2026

Send Emails with SendGrid API in Python

Sending email is a core feature of almost every web application. Users need welcome messages, password resets, order confirmations, and notifications.

SendGrid is one of the most reliable email APIs available. Its official Python library makes sending email simple and clean.

This guide walks you through sending emails with the SendGrid API. You will learn the client setup, HTML content, attachments, dynamic templates, and how to handle errors.

What is the SendGrid API?

The SendGrid API is a REST interface for sending email at scale. It handles deliverability, bounce tracking, and analytics for you.

The Python SDK is called sendgrid. It wraps the Web API v3 into friendly Python classes. You build a Mail() object and send it with a client instance.

The free tier allows 100 emails per day. That is plenty for testing and small projects.

If you have not installed the library yet, see this guide on how to install SendGrid in Python.

Set Up Your API Key

You need a SendGrid account and an API key. Create one in the dashboard with Mail Send Full Access permissions.

Never hardcode the key in your source code. Store it in an environment variable instead.

 export SENDGRID_API_KEY='your_api_key_here' 

On Windows, use set instead of export. For permanent storage, add the line to your shell profile or use a .env file.

 from dotenv import load_dotenv import os # Load variables from .env file load_dotenv() api_key = os.environ.get('SENDGRID_API_KEY') 

This keeps your secret safe and your code portable across environments.

Send Your First Email

The Mail() helper builds the message. The SendGridAPIClient() class handles authentication and delivery.

 import os from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail # Build the email message message = Mail( from_email='verified@yourdomain.com', to_emails='recipient@example.com', subject='Hello from SendGrid', plain_text_content='This email was sent with Python!' ) # Create the client and send sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) response = sg.send(message) print(response.status_code) 
 202 

A status code of 202 means SendGrid accepted the message. That is the success response.

Important: The from_email address must be a verified sender in your account. Unverified senders return a 403 error.

Sending HTML Emails

Plain text works, but HTML makes your emails look professional. Pass your markup to the html_content field.

 message = Mail( from_email='verified@yourdomain.com', to_emails='customer@example.com', subject='Your Invoice', html_content=''' 

Thank You for Your Order

Your order #12345 has shipped.

Track it here.

''' )

The send() method accepts the message and returns a response object. Always log the status code for debugging.

You can include both plain text and HTML. SendGrid sends the best version for each recipient's email client.

 message = Mail( from_email='verified@yourdomain.com', to_emails='customer@example.com', subject='Welcome', plain_text_content='Welcome to our service!', html_content='

Welcome!

We are glad you joined.

' )

This is best practice. Some clients block HTML, and plain text is a reliable fallback.

Sending to Multiple Recipients

You can send to a list of addresses in one API call. Pass a list to the to_emails parameter.

 recipients = ['alice@example.com', 'bob@example.com', 'carol@example.com'] message = Mail( from_email='verified@yourdomain.com', to_emails=recipients, subject='Team Update', html_content='

Weekly update attached below.

' ) sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) response = sg.send(message)

Every recipient sees the same message. For personalized emails, use dynamic templates or loop through recipients individually.

Bulk sending across many contacts can be slow. If you are iterating over thousands of records, Python loops can become a bottleneck. Concurrency with asyncio or threads is usually the better fit for I/O-bound work like email delivery.

Adding Attachments

SendGrid supports file attachments. Encode the file in base64 and add it with the Attachment() helper.

 import base64 from sendgrid.helpers.mail import ( Attachment, FileContent, FileName, FileType, Disposition ) # Read and encode the file with open('invoice.pdf', 'rb') as f: data = f.read() encoded = base64.b64encode(data).decode() # Build the attachment attachment = Attachment() attachment.file_content = FileContent(encoded) attachment.file_name = FileName('invoice.pdf') attachment.file_type = FileType('application/pdf') attachment.disposition = Disposition('attachment') # Attach to the message message.attachment = attachment # Send response = sg.send(message) 

This is ideal for invoices, reports, and receipts. If you need to generate those PDFs first, you can generate PDFs with ReportLab and attach the result directly.

Keep attachment sizes reasonable. Most email providers cap total message size at 25 MB. SendGrid also has its own limits.

Using Dynamic Templates

Dynamic templates separate design from code. You build the email in the SendGrid dashboard and fill it with data from Python.

Each template has an ID starting with d-. You pass that ID and a dictionary of values.

 message = Mail( from_email='verified@yourdomain.com', to_emails='customer@example.com', subject='Your Order is Ready' ) message.template_id = 'd-abc123def456' message.dynamic_template_data = { 'customer_name': 'Jane', 'order_id': 'ORD-2024-001', 'total': '$49.99' } response = sg.send(message) 

Templates use Handlebars syntax. They support conditionals, loops, and fallback values. This makes them powerful for personalized emails at scale.

Note: The subject in the Mail object is ignored when using a template. Set the subject inside the template itself.

Scheduling Emails

You can schedule an email to send at a future time. Use the SendAt() helper with a Unix timestamp.

 import time from sendgrid.helpers.mail import SendAt # Schedule for 1 hour from now future_time = int(time.time()) + 3600 message = Mail( from_email='verified@yourdomain.com', to_emails='customer@example.com', subject='Reminder', html_content='

This is your scheduled reminder.

' ) message.send_at = SendAt(future_time) response = sg.send(message)

This is useful for drip campaigns, appointment reminders, and timed notifications.

Handling Errors Properly

Network issues and API limits happen. Wrap your send call in a try block and handle exceptions gracefully.

 from python_http_client.exceptions import HTTPError try: response = sg.send(message) print(f"Status: {response.status_code}") except HTTPError as e: print(f"Error code: {e.status_code}") print(f"Reason: {e.reason}") print(f"Body: {e.body}") except Exception as e: print(f"Unexpected error: {e}") 

Log the status code and body for every failure. This makes debugging much faster.

The HTTPError class exposes the status code, reason phrase, and raw response body. Use those fields to build helpful alerts.

Common Errors and How to Fix Them

401 Unauthorized: The API key is invalid, missing, or lacks Mail Send permission. Verify the environment variable is set.

403 Forbidden: The sender email is not verified. Verify the from address or authenticate your domain.

400 Bad Request: A required field is missing. Check from_email, to_emails, subject, and content.

413 Payload Too Large: The attachments exceed the size limit. Compress files or host them for download instead.

429 Too Many Requests: You hit the rate limit. Back off and retry after a short delay.

Quota exceeded: You reached your daily sending limit. Check the dashboard and upgrade if needed.

If your email workflow involves incoming documents, you may also want to install pdfplumber in Python to parse PDF attachments and extract their contents before sending a summary.

Best Practices for Reliable Delivery

Authenticate your sending domain with SPF and DKIM records. This dramatically improves inbox placement.

Always include a plain text version of every HTML email. It is a good fallback and improves accessibility.

Handle bounces and unsubscribes. SendGrid sends webhook events you can process in your application.

Use a dedicated IP for high volume sending. It protects your reputation from other senders.

Keep your email lists clean. Remove hard bounces and inactive addresses regularly.

Test with a small batch before sending to your full list. This catches formatting issues early.

Conclusion

Sending emails with the SendGrid API in Python is simple and reliable. The library gives you clean helpers for messages, attachments, templates, and scheduling.

Build your message with the Mail() helper, authenticate with SendGridAPIClient(), and send with the send() method. That is the whole flow.

Store your API key securely. Verify your sender. Handle errors gracefully. Follow these rules and your emails will reach inboxes reliably.

Start with the simple examples in this guide. Then add HTML content, attachments, and dynamic templates as your needs grow. Happy coding!