What's Actually Happening
SendGrid API accepts email requests but emails are not delivered. Recipients don't receive emails sent through SendGrid.
The Error You'll See
```python import sendgrid sg = sendgrid.SendGridAPIClient(api_key='SG.xxx') response = sg.send(message)
# Response 202 but no email received ```
API response:
{
"message": "success"
}
// But email never arrivesBounce notification:
Email bounced: 550 5.7.1 Unauthenticated email from domain.comNo logs:
# SendGrid Activity Feed shows no entriesWhy This Happens
- 1.Sender not verified - Domain/email not authenticated
- 2.API key invalid - Wrong or revoked API key
- 3.SPF/DKIM not configured - Email authentication missing
- 4.Email in spam - Delivered but marked as spam
- 5.Rate limiting - Sending limit exceeded
- 6.Invalid recipient - Wrong email address
Step 1: Verify API Key
```bash # Test API key: curl -X "GET" "https://api.sendgrid.com/v3/user/account" \ -H "Authorization: Bearer YOUR_API_KEY"
# Should return account info, not 401
# Check API key permissions: # SendGrid Dashboard -> Settings -> API Keys
# Required permissions: # - Mail Send: Mail Send # - Mail Send: Template Engine (if using templates)
# Create new API key: curl -X POST https://api.sendgrid.com/v3/api_keys \ -H "Authorization: Bearer PARENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"new-key","scopes":["mail.send"]}' ```
Step 2: Check Sender Verification
```bash # Check verified senders: curl -X GET "https://api.sendgrid.com/v3/verified_senders" \ -H "Authorization: Bearer YOUR_API_KEY"
# In SendGrid Dashboard: # Settings -> Sender Authentication
# Option 1: Single Sender Verification: # Settings -> Sender Authentication -> Verify a Single Sender
# Option 2: Domain Authentication: # Settings -> Sender Authentication -> Authenticate a Domain
# Add DNS records: # - CNAME record for DKIM # - CNAME record for domain verification
# Verify DNS records: dig CNAME s1._domainkey.yourdomain.com dig CNAME s2._domainkey.yourdomain.com
# Check authentication status: curl -X GET "https://api.sendgrid.com/v3/whitelabel/domains" \ -H "Authorization: Bearer YOUR_API_KEY" ```
Step 3: Configure SPF and DKIM
```bash # SPF Record - Add to DNS TXT:
yourdomain.com. IN TXT "v=spf1 include:sendgrid.net -all"
# Check SPF: dig TXT yourdomain.com | grep spf
# DKIM - SendGrid provides CNAME records:
# Add these CNAME records: s1._domainkey.yourdomain.com -> s1.domainkey.sendgrid.net s2._domainkey.yourdomain.com -> s2.domainkey.sendgrid.net
# Check DKIM: dig CNAME s1._domainkey.yourdomain.com
# DMARC (recommended): _dmarc.yourdomain.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com"
# Verify with online tools: # https://mxtoolbox.com/SenderPolicyFramework.aspx # https://mxtoolbox.com/dkim.aspx
# In SendGrid Dashboard: # Settings -> Sender Authentication -> Domain Authentication ```
Step 4: Check Email Activity
```bash # View email activity: curl -X GET "https://api.sendgrid.com/v3/messages?limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"
# Check for specific email: curl -X GET "https://api.sendgrid.com/v3/messages?query=to_email%3Arecipient%40example.com" \ -H "Authorization: Bearer YOUR_API_KEY"
# In Dashboard: # Activity -> Email Activity
# Filter by: # - Processed # - Delivered # - Opened # - Clicked # - Bounced # - Dropped # - Spam Report
# Check suppression list: curl -X GET "https://api.sendgrid.com/v3/suppression/bounces" \ -H "Authorization: Bearer YOUR_API_KEY"
# Check blocks: curl -X GET "https://api.sendgrid.com/v3/suppression/blocks" \ -H "Authorization: Bearer YOUR_API_KEY" ```
Step 5: Test Email Sending
```bash # Test with curl: curl -X POST https://api.sendgrid.com/v3/mail/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "personalizations": [{ "to": [{"email": "recipient@example.com"}], "subject": "Test Email" }], "from": {"email": "sender@yourdomain.com"}, "content": [{ "type": "text/plain", "value": "This is a test email" }] }'
# Response should be 202 Accepted
# Test with Python: import sendgrid from sendgrid.helpers.mail import Mail
sg = sendgrid.SendGridAPIClient(api_key='SG.xxx') message = Mail( from_email='sender@yourdomain.com', to_emails='recipient@example.com', subject='Test Email', html_content='<strong>Test content</strong>' ) response = sg.send(message) print(response.status_code) # Should be 202 ```
Step 6: Check Spam Folders
```bash # Email might be delivered to spam
# Check spam score: # Use mail-tester.com or similar
# Improve deliverability:
# 1. Use proper From name: "Company Name <noreply@yourdomain.com>"
# 2. Add plain text version: "content": [ {"type": "text/plain", "value": "Plain text version"}, {"type": "text/html", "value": "<html>...</html>"} ]
# 3. Avoid spam triggers: # - ALL CAPS # - Too many links # - Spam trigger words (FREE, CLICK NOW, etc.)
# 4. Include unsubscribe link: "content": [{ "type": "text/html", "value": "... <a href='{{{unsubscribe}}}'>Unsubscribe</a>" }]
# 5. Set proper headers: "headers": { "List-Unsubscribe": "<mailto:unsubscribe@yourdomain.com>" } ```
Step 7: Check Rate Limits
```bash # Check account limits: curl -X GET "https://api.sendgrid.com/v3/user/credits" \ -H "Authorization: Bearer YOUR_API_KEY"
# Free tier limits: # - 100 emails/day # - Limited to 30 days trial
# Check sending limits: curl -X GET "https://api.sendgrid.com/v3/user/email_limits" \ -H "Authorization: Bearer YOUR_API_KEY"
# If hitting limits: # - Upgrade plan # - Wait for limit reset (daily)
# Check for throttling in response: # 429 Too Many Requests
# Implement retry logic: import time from sendgrid.exceptions import SendGridException
max_retries = 3 for attempt in range(max_retries): try: response = sg.send(message) break except SendGridException as e: if e.status_code == 429: time.sleep(60) continue raise ```
Step 8: Check Suppression Lists
```bash # List bounces: curl -X GET "https://api.sendgrid.com/v3/suppression/bounces" \ -H "Authorization: Bearer YOUR_API_KEY"
# Remove from bounce list: curl -X DELETE "https://api.sendgrid.com/v3/suppression/bounces/recipient@example.com" \ -H "Authorization: Bearer YOUR_API_KEY"
# Check blocks: curl -X GET "https://api.sendgrid.com/v3/suppression/blocks" \ -H "Authorization: Bearer YOUR_API_KEY"
# Check spam reports: curl -X GET "https://api.sendgrid.com/v3/suppression/spam_reports" \ -H "Authorization: Bearer YOUR_API_KEY"
# Check unsubscribes: curl -X GET "https://api.sendgrid.com/v3/suppression/unsubscribes" \ -H "Authorization: Bearer YOUR_API_KEY"
# Delete from suppression list: curl -X DELETE "https://api.sendgrid.com/v3/suppression/blocks/recipient@example.com" \ -H "Authorization: Bearer YOUR_API_KEY" ```
Step 9: Use Webhooks for Tracking
```python # Set up webhook to track email events: # Settings -> Mail Settings -> Event Notification
# Webhook events: { "email": "recipient@example.com", "event": "delivered", # or bounced, dropped, etc. "sg_event_id": "xxx", "sg_message_id": "xxx", "reason": "...", # For bounces "status": "..." # HTTP status }
# Events to monitor: # - processed - Email received by SendGrid # - delivered - Email delivered to recipient server # - opened - Email opened # - clicked - Link clicked # - bounced - Email bounced # - dropped - Email not sent # - deferred - Delivery deferred # - spamreport - Marked as spam # - unsubscribe - Recipient unsubscribed
# Flask webhook handler: from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST']) def webhook(): events = request.json for event in events: print(f"Event: {event['event']} for {event['email']}") return jsonify({'status': 'ok'}) ```
Step 10: SendGrid Verification Script
```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-sendgrid.sh #!/bin/bash
API_KEY=$1
echo "=== API Key Test ===" curl -s -X GET "https://api.sendgrid.com/v3/user/account" \ -H "Authorization: Bearer $API_KEY" | jq .
echo "" echo "=== Sender Authentication ===" curl -s -X GET "https://api.sendgrid.com/v3/whitelabel/domains" \ -H "Authorization: Bearer $API_KEY" | jq '.[] | {domain: .domain, valid: .valid}'
echo "" echo "=== Verified Senders ===" curl -s -X GET "https://api.sendgrid.com/v3/verified_senders" \ -H "Authorization: Bearer $API_KEY" | jq '.results[] | .email'
echo "" echo "=== Recent Activity ===" curl -s -X GET "https://api.sendgrid.com/v3/messages?limit=5" \ -H "Authorization: Bearer $API_KEY" | jq '.messages[] | {to: .to_email, status: .status}'
echo "" echo "=== Suppression Counts ===" echo "Bounces: $(curl -s -X GET "https://api.sendgrid.com/v3/suppression/bounces" -H "Authorization: Bearer $API_KEY" | jq 'length')" echo "Blocks: $(curl -s -X GET "https://api.sendgrid.com/v3/suppression/blocks" -H "Authorization: Bearer $API_KEY" | jq 'length')" echo "Spam Reports: $(curl -s -X GET "https://api.sendgrid.com/v3/suppression/spam_reports" -H "Authorization: Bearer $API_KEY" | jq 'length')"
echo "" echo "=== Recommendations ===" echo "1. Verify sender domain authentication" echo "2. Check SPF/DKIM/DMARC records" echo "3. Review suppression lists" echo "4. Test with different recipient" echo "5. Check spam folder" echo "6. Review email content for spam triggers" EOF
chmod +x /usr/local/bin/check-sendgrid.sh
# Usage: /usr/local/bin/check-sendgrid.sh SG.xxx ```
SendGrid Checklist
| Check | Expected |
|---|---|
| API key valid | Account info returned |
| Sender verified | In verified senders list |
| Domain authenticated | DNS records present |
| SPF record | v=spf1 include:sendgrid.net |
| DKIM CNAME | Records resolve |
| Not in suppression | Recipient not blocked |
Verify the Fix
```bash # After fixing SendGrid issues
# 1. Test API key curl -X GET "https://api.sendgrid.com/v3/user/account" -H "Authorization: Bearer KEY" // Account info returned
# 2. Check sender verification curl -X GET "https://api.sendgrid.com/v3/verified_senders" -H "Authorization: Bearer KEY" // Sender listed
# 3. Send test email curl -X POST https://api.sendgrid.com/v3/mail/send ... // 202 Accepted
# 4. Check activity # Dashboard -> Activity // Email shows delivered
# 5. Check recipient inbox // Email in inbox (check spam)
# 6. Test various recipients // Emails delivered to all ```
Related Issues
- [Fix Email Not Sending](/articles/fix-email-not-sending)
- [Fix Email SPF Record Not Validating](/articles/fix-email-spf-record-not-validating)
- [Fix Email Bounce Error](/articles/fix-email-bounce-error)