Gönderi

Python ile E-posta Otomasyonu

Python ile SMTP, HTML şablonları ve e-posta servisleri kullanarak profesyonel e-posta otomasyon sistemleri geliştirme. Jinja2 template'leri, toplu gönderim, SendGrid/Mailgun entegrasyonu ve best practices.

Python ile E-posta Otomasyonu

E-posta otomasyonu, modern uygulamalarda kullanıcı bildirimleri, raporlama, alert sistemleri ve pazarlama kampanyaları için vazgeçilmez bir özelliktir. Python, SMTP protokolü ve çeşitli e-posta servisleri ile güçlü otomasyon çözümleri sunar. Bu yazıda, Python ile e-posta gönderimi, HTML şablonları, toplu e-posta, error handling ve best practice’leri ele alacağız.

SMTP Protokolü ve Python smtplib

SMTP (Simple Mail Transfer Protocol), e-posta göndermek için kullanılan standart protokoldür. Python’ın built-in smtplib modülü ile kolayca e-posta gönderebilirsiniz.

Temel E-posta Gönderimi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import smtplib
from email.message import EmailMessage

def send_simple_email():
    """Basit metin e-postası gönder"""
    # Email mesajı oluştur
    msg = EmailMessage()
    msg['Subject'] = 'Test E-postası'
    msg['From'] = '[email protected]'
    msg['To'] = '[email protected]'
    msg.set_content('Bu bir test e-postasıdır.')
    
    # SMTP sunucusuna bağlan ve gönder
    with smtplib.SMTP('smtp.gmail.com', 587) as smtp:
        smtp.starttls()  # TLS encryption başlat
        smtp.login('[email protected]', 'uygulama_sifresi')
        smtp.send_message(msg)
        print("E-posta başarıyla gönderildi!")

Gmail ile e-posta göndermek için normal şifre yerine uygulama şifresi (app password) kullanmalısınız. Google Hesabı Güvenliği sayfasından oluşturabilirsiniz.

Çoklu Alıcı ve CC/BCC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from email.message import EmailMessage
import smtplib

def send_multi_recipient_email():
    """Çoklu alıcıya e-posta gönder"""
    msg = EmailMessage()
    msg['Subject'] = 'Önemli Duyuru'
    msg['From'] = '[email protected]'
    
    # Birden fazla alıcı
    msg['To'] = '[email protected], [email protected]'
    
    # CC (Carbon Copy) - Görünür kopya
    msg['Cc'] = '[email protected]'
    
    # BCC (Blind Carbon Copy) - Gizli kopya
    # BCC başlıkta görünmez, send_message parametresinde belirtilir
    bcc_recipients = ['[email protected]', '[email protected]']
    
    msg.set_content("""
    Merhaba Takım,
    
    Bu hafta sonu planlı bakım çalışması olacaktır.
    
    Saygılarımızla,
    IT Ekibi
    """)
    
    # Tüm alıcıları birleştir
    all_recipients = (
        msg['To'].split(', ') + 
        msg['Cc'].split(', ') + 
        bcc_recipients
    )
    
    with smtplib.SMTP('smtp.gmail.com', 587) as smtp:
        smtp.starttls()
        smtp.login('[email protected]', 'app_password')
        smtp.send_message(msg, to_addrs=all_recipients)
    
    print(f"E-posta {len(all_recipients)} kişiye gönderildi")

BCC (Blind Carbon Copy) alıcıları e-posta başlığında görünmez, ancak send_message() fonksiyonunun to_addrs parametresine eklenmelidir.

HTML E-posta ve Template Rendering

Profesyonel e-postalar için HTML içerik ve template engine kullanımı önemlidir.

Jinja2 Email Template Rendering Jinja2 ile e-posta template rendering süreci

Jinja2 ile HTML Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
from jinja2 import Template
from email.message import EmailMessage
from email.utils import make_msgid
import smtplib

# HTML template
EMAIL_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
    <style>
        body {
            font-family: Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
        }
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
            border-radius: 10px 10px 0 0;
        }
        .content {
            background: #f9f9f9;
            padding: 30px;
            border-radius: 0 0 10px 10px;
        }
        .button {
            display: inline-block;
            padding: 12px 30px;
            background: #667eea;
            color: white;
            text-decoration: none;
            border-radius: 5px;
            margin: 20px 0;
        }
        .footer {
            text-align: center;
            margin-top: 20px;
            color: #888;
            font-size: 12px;
        }
        .stats {
            display: flex;
            justify-content: space-around;
            margin: 20px 0;
        }
        .stat-box {
            text-align: center;
            padding: 15px;
            background: white;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        .stat-number {
            font-size: 24px;
            font-weight: bold;
            color: #667eea;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1></h1>
        <p></p>
    </div>
    
    <div class="content">
        <p>Merhaba ,</p>
        
        <p></p>
        
        
        
        
        
        <p>İyi çalışmalar dileriz!</p>
    </div>
    
    <div class="footer">
        <p>Bu e-posta  tarafından gönderilmiştir.</p>
        <p> © Tüm hakları saklıdır.</p>
    </div>
</body>
</html>
"""

def send_html_email(to_email, context):
    """HTML template ile e-posta gönder"""
    # Template render et
    template = Template(EMAIL_TEMPLATE)
    html_content = template.render(**context)
    
    # Email mesajı oluştur
    msg = EmailMessage()
    msg['Subject'] = context['title']
    msg['From'] = '[email protected]'
    msg['To'] = to_email
    
    # Plain text fallback
    msg.set_content(f"""
    {context['title']}
    
    Merhaba {context['user_name']},
    
    {context['message']}
    
    {context.get('action_url', '')}
    """)
    
    # HTML içerik ekle
    msg.add_alternative(html_content, subtype='html')
    
    # E-postayı gönder
    with smtplib.SMTP('smtp.gmail.com', 587) as smtp:
        smtp.starttls()
        smtp.login('[email protected]', 'app_password')
        smtp.send_message(msg)

# Kullanım örneği
context = {
    'title': 'Haftalık Rapor',
    'subtitle': '1-7 Kasım 2025',
    'user_name': 'Ahmet Yılmaz',
    'message': 'Bu hafta harika bir performans sergiledsiniz!',
    'stats': [
        {'value': '127', 'label': 'Tamamlanan Görev'},
        {'value': '98%', 'label': 'Başarı Oranı'},
        {'value': '4.8', 'label': 'Ortalama Puan'}
    ],
    'action_url': 'https://dashboard.company.com',
    'action_text': 'Dashboard\'a Git',
    'company_name': 'ABC Şirketi',
    'year': 2025
}

send_html_email('[email protected]', context)

Ek Dosya (Attachment) Gönderimi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from email.message import EmailMessage
import smtplib
from pathlib import Path
import mimetypes

def send_email_with_attachments(
    to_email,
    subject,
    body,
    attachments=None
):
    """Ek dosyalarla e-posta gönder"""
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = '[email protected]'
    msg['To'] = to_email
    msg.set_content(body)
    
    # Ek dosyaları ekle
    if attachments:
        for file_path in attachments:
            path = Path(file_path)
            
            # MIME type'ı belirle
            mime_type, _ = mimetypes.guess_type(file_path)
            if mime_type is None:
                mime_type = 'application/octet-stream'
            
            maintype, subtype = mime_type.split('/', 1)
            
            # Dosyayı oku ve ekle
            with open(file_path, 'rb') as f:
                msg.add_attachment(
                    f.read(),
                    maintype=maintype,
                    subtype=subtype,
                    filename=path.name
                )
    
    # Gönder
    with smtplib.SMTP('smtp.gmail.com', 587) as smtp:
        smtp.starttls()
        smtp.login('[email protected]', 'app_password')
        smtp.send_message(msg)
    
    print(f"E-posta gönderildi: {len(attachments or [])} ek dosya")

# Kullanım
send_email_with_attachments(
    to_email='[email protected]',
    subject='Aylık Rapor',
    body='Ekteki raporları inceleyebilirsiniz.',
    attachments=[
        'reports/november_sales.pdf',
        'reports/november_analytics.xlsx',
        'reports/summary.docx'
    ]
)

# In-memory attachment (pandas DataFrame örneği)
import io
import pandas as pd

def send_dataframe_as_excel(to_email, df, filename='report.xlsx'):
    """DataFrame'i Excel eki olarak gönder"""
    msg = EmailMessage()
    msg['Subject'] = 'Veri Raporu'
    msg['From'] = '[email protected]'
    msg['To'] = to_email
    msg.set_content('Ekteki Excel dosyasını inceleyebilirsiniz.')
    
    # DataFrame'i memory'de Excel'e çevir
    excel_buffer = io.BytesIO()
    with pd.ExcelWriter(excel_buffer, engine='openpyxl') as writer:
        df.to_excel(writer, index=False, sheet_name='Data')
    
    excel_buffer.seek(0)
    
    # Excel dosyasını ekle
    msg.add_attachment(
        excel_buffer.read(),
        maintype='application',
        subtype='vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        filename=filename
    )
    
    # Gönder
    with smtplib.SMTP('smtp.gmail.com', 587) as smtp:
        smtp.starttls()
        smtp.login('[email protected]', 'app_password')
        smtp.send_message(msg)

Büyük ek dosyalar (>10MB) gönderirken SMTP sunucularının boyut limitlerini kontrol edin. Gerekirse dosyaları sıkıştırın veya cloud storage linki gönderin.

Toplu E-posta Gönderimi

Email Automation Architecture Python ile e-posta otomasyon mimarisi Python ile e-posta otomasyon mimarisi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import smtplib
from email.message import EmailMessage
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
from dataclasses import dataclass
from typing import List, Dict
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class EmailRecipient:
    """E-posta alıcı bilgileri"""
    email: str
    name: str
    custom_data: Dict = None

class BulkEmailSender:
    """Toplu e-posta gönderme sınıfı"""
    
    def __init__(
        self,
        smtp_host='smtp.gmail.com',
        smtp_port=587,
        username=None,
        password=None,
        rate_limit=10  # Saniyede max e-posta
    ):
        self.smtp_host = smtp_host
        self.smtp_port = smtp_port
        self.username = username
        self.password = password
        self.rate_limit = rate_limit
        self.sent_count = 0
        self.failed_count = 0
    
    def send_single_email(
        self,
        recipient: EmailRecipient,
        subject: str,
        template: str
    ) -> bool:
        """Tek bir e-posta gönder"""
        try:
            # Template'i render et
            from jinja2 import Template
            content = Template(template).render(
                name=recipient.name,
                email=recipient.email,
                **(recipient.custom_data or {})
            )
            
            # Email oluştur
            msg = EmailMessage()
            msg['Subject'] = subject
            msg['From'] = self.username
            msg['To'] = recipient.email
            msg.add_alternative(content, subtype='html')
            
            # Gönder
            with smtplib.SMTP(self.smtp_host, self.smtp_port) as smtp:
                smtp.starttls()
                smtp.login(self.username, self.password)
                smtp.send_message(msg)
            
            self.sent_count += 1
            logger.info(f"✓ E-posta gönderildi: {recipient.email}")
            return True
            
        except Exception as e:
            self.failed_count += 1
            logger.error(f"✗ Hata ({recipient.email}): {str(e)}")
            return False
    
    def send_bulk(
        self,
        recipients: List[EmailRecipient],
        subject: str,
        template: str,
        max_workers=5
    ) -> Dict:
        """Toplu e-posta gönder"""
        logger.info(f"Toplu e-posta başlıyor: {len(recipients)} alıcı")
        start_time = time.time()
        
        # Rate limiting için delay hesapla
        delay_between_emails = 1.0 / self.rate_limit
        
        results = []
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # Tüm görevleri submit et
            future_to_recipient = {
                executor.submit(
                    self.send_single_email,
                    recipient,
                    subject,
                    template
                ): recipient
                for recipient in recipients
            }
            
            # Sonuçları topla
            for future in as_completed(future_to_recipient):
                recipient = future_to_recipient[future]
                try:
                    success = future.result()
                    results.append({
                        'email': recipient.email,
                        'success': success
                    })
                    
                    # Rate limiting
                    time.sleep(delay_between_emails)
                    
                except Exception as e:
                    logger.error(f"Unexpected error: {str(e)}")
                    results.append({
                        'email': recipient.email,
                        'success': False
                    })
        
        elapsed = time.time() - start_time
        
        summary = {
            'total': len(recipients),
            'sent': self.sent_count,
            'failed': self.failed_count,
            'elapsed_seconds': elapsed,
            'emails_per_second': len(recipients) / elapsed,
            'results': results
        }
        
        logger.info(f"""
        Toplu e-posta tamamlandı:
        - Gönderilen: {self.sent_count}
        - Başarısız: {self.failed_count}
        - Süre: {elapsed:.2f} saniye
        - Hız: {summary['emails_per_second']:.2f} e-posta/saniye
        """)
        
        return summary

# Kullanım örneği
if __name__ == '__main__':
    # Alıcı listesi
    recipients = [
        EmailRecipient(
            email='[email protected]',
            name='Ahmet Yılmaz',
            custom_data={'subscription': 'Premium', 'score': 95}
        ),
        EmailRecipient(
            email='[email protected]',
            name='Ayşe Demir',
            custom_data={'subscription': 'Basic', 'score': 87}
        ),
        # ... daha fazla alıcı
    ]
    
    # HTML template
    template = """
    <html>
    <body>
        <h2>Merhaba !</h2>
        <p> aboneliğiniz için teşekkürler.</p>
        <p>Bu ayki puanınız: <strong></strong></p>
    </body>
    </html>
    """
    
    # Toplu gönder
    sender = BulkEmailSender(
        username='[email protected]',
        password='app_password',
        rate_limit=10  # 10 e-posta/saniye
    )
    
    results = sender.send_bulk(
        recipients=recipients,
        subject='Aylık Performans Raporu',
        template=template,
        max_workers=3
    )

Email Service Provider Entegrasyonları

Production’da SMTP yerine özel e-posta servisleri kullanmak daha güvenilirdir.

SendGrid Entegrasyonu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Email, To, Content

class SendGridEmailer:
    """SendGrid ile e-posta gönderimi"""
    
    def __init__(self, api_key):
        self.client = SendGridAPIClient(api_key)
    
    def send_simple(self, to_email, subject, html_content):
        """Basit e-posta gönder"""
        message = Mail(
            from_email='[email protected]',
            to_emails=to_email,
            subject=subject,
            html_content=html_content
        )
        
        try:
            response = self.client.send(message)
            return {
                'status_code': response.status_code,
                'success': 200 <= response.status_code < 300
            }
        except Exception as e:
            return {'success': False, 'error': str(e)}
    
    def send_with_template(
        self,
        to_email,
        template_id,
        dynamic_data
    ):
        """SendGrid template ile gönder"""
        message = Mail(
            from_email='[email protected]',
            to_emails=to_email
        )
        
        message.template_id = template_id
        message.dynamic_template_data = dynamic_data
        
        try:
            response = self.client.send(message)
            return {'success': True, 'status': response.status_code}
        except Exception as e:
            return {'success': False, 'error': str(e)}

# Kullanım
sendgrid = SendGridEmailer(api_key='SG.xxx')

result = sendgrid.send_simple(
    to_email='[email protected]',
    subject='Hoş Geldiniz!',
    html_content='<h1>Aramıza hoş geldiniz!</h1>'
)

Mailgun Entegrasyonu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import requests

class MailgunEmailer:
    """Mailgun ile e-posta gönderimi"""
    
    def __init__(self, api_key, domain):
        self.api_key = api_key
        self.domain = domain
        self.base_url = f'https://api.mailgun.net/v3/{domain}'
    
    def send_email(
        self,
        to_email,
        subject,
        html_content,
        text_content=None,
        attachments=None
    ):
        """E-posta gönder"""
        data = {
            'from': f'System <noreply@{self.domain}>',
            'to': to_email,
            'subject': subject,
            'html': html_content
        }
        
        if text_content:
            data['text'] = text_content
        
        files = None
        if attachments:
            files = [
                ('attachment', open(f, 'rb'))
                for f in attachments
            ]
        
        response = requests.post(
            f'{self.base_url}/messages',
            auth=('api', self.api_key),
            data=data,
            files=files
        )
        
        return {
            'success': response.status_code == 200,
            'response': response.json()
        }
    
    def send_bulk(self, recipients, subject, html_content):
        """Toplu e-posta (recipient variables)"""
        # Mailgun bulk gönderim
        data = {
            'from': f'Newsletter <noreply@{self.domain}>',
            'to': [r['email'] for r in recipients],
            'subject': subject,
            'html': html_content,
            'recipient-variables': {
                r['email']: r['data']
                for r in recipients
            }
        }
        
        response = requests.post(
            f'{self.base_url}/messages',
            auth=('api', self.api_key),
            data=data
        )
        
        return response.json()

# Kullanım
mailgun = MailgunEmailer(
    api_key='key-xxx',
    domain='mg.company.com'
)

result = mailgun.send_email(
    to_email='[email protected]',
    subject='Test E-posta',
    html_content='<h1>Merhaba Dünya!</h1>',
    attachments=['report.pdf']
)

SendGrid ve Mailgun gibi e-posta servisleri, SMTP’ye göre daha yüksek deliverability, detaylı analytics ve template yönetimi sunar. Production ortamları için önerilir.

Error Handling ve Retry Logic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import smtplib
from email.message import EmailMessage
import time
from functools import wraps
import logging

logger = logging.getLogger(__name__)

class EmailSendError(Exception):
    """E-posta gönderme hatası"""
    pass

def retry_on_failure(max_retries=3, delay=2, backoff=2):
    """Retry decorator"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            retries = 0
            current_delay = delay
            
            while retries < max_retries:
                try:
                    return func(*args, **kwargs)
                except (smtplib.SMTPException, ConnectionError) as e:
                    retries += 1
                    if retries >= max_retries:
                        logger.error(
                            f"Failed after {max_retries} retries: {str(e)}"
                        )
                        raise EmailSendError(
                            f"Could not send email after {max_retries} attempts"
                        )
                    
                    logger.warning(
                        f"Attempt {retries} failed: {str(e)}. "
                        f"Retrying in {current_delay}s..."
                    )
                    time.sleep(current_delay)
                    current_delay *= backoff
            
        return wrapper
    return decorator

class RobustEmailSender:
    """Hata yönetimi olan e-posta gönderici"""
    
    def __init__(self, smtp_config):
        self.smtp_config = smtp_config
    
    @retry_on_failure(max_retries=3, delay=2, backoff=2)
    def send_email(self, msg):
        """Retry logic ile e-posta gönder"""
        try:
            with smtplib.SMTP(
                self.smtp_config['host'],
                self.smtp_config['port'],
                timeout=30
            ) as smtp:
                smtp.starttls()
                smtp.login(
                    self.smtp_config['username'],
                    self.smtp_config['password']
                )
                smtp.send_message(msg)
                
            logger.info(f"Email sent successfully to {msg['To']}")
            return True
            
        except smtplib.SMTPAuthenticationError:
            logger.error("SMTP authentication failed")
            raise
        
        except smtplib.SMTPRecipientsRefused:
            logger.error(f"Recipient refused: {msg['To']}")
            raise
        
        except smtplib.SMTPServerDisconnected:
            logger.warning("SMTP server disconnected, will retry")
            raise ConnectionError("SMTP disconnected")
        
        except Exception as e:
            logger.error(f"Unexpected error: {str(e)}")
            raise
    
    def send_with_fallback(self, msg, fallback_config=None):
        """Ana sunucu başarısız olursa fallback sunucu dene"""
        try:
            return self.send_email(msg)
        except Exception as e:
            if fallback_config:
                logger.warning(
                    f"Primary SMTP failed, trying fallback: {str(e)}"
                )
                # Geçici olarak fallback config kullan
                original_config = self.smtp_config
                self.smtp_config = fallback_config
                try:
                    return self.send_email(msg)
                finally:
                    self.smtp_config = original_config
            else:
                raise

# Kullanım
smtp_config = {
    'host': 'smtp.gmail.com',
    'port': 587,
    'username': '[email protected]',
    'password': 'app_password'
}

fallback_config = {
    'host': 'smtp.sendgrid.net',
    'port': 587,
    'username': 'apikey',
    'password': 'SG.xxx'
}

sender = RobustEmailSender(smtp_config)

msg = EmailMessage()
msg['Subject'] = 'Test'
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
msg.set_content('Test message')

try:
    sender.send_with_fallback(msg, fallback_config)
except EmailSendError as e:
    logger.error(f"Could not send email: {str(e)}")

SMTP sunucuları geçici olarak kullanılamayabilir. Retry logic ve fallback sunucu konfigürasyonu ile sistem güvenilirliğini artırın.

Production Best Practices

1. Email Queue Sistemi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import redis
import json
from datetime import datetime
import uuid

class EmailQueue:
    """Redis ile e-posta kuyruğu"""
    
    def __init__(self, redis_client):
        self.redis = redis_client
        self.queue_key = 'email:queue'
        self.processing_key = 'email:processing'
        self.failed_key = 'email:failed'
    
    def enqueue(self, email_data):
        """E-postayı kuyruğa ekle"""
        email_id = str(uuid.uuid4())
        email_job = {
            'id': email_id,
            'data': email_data,
            'created_at': datetime.utcnow().isoformat(),
            'attempts': 0
        }
        
        self.redis.lpush(
            self.queue_key,
            json.dumps(email_job)
        )
        
        return email_id
    
    def dequeue(self):
        """Kuyruktan e-posta al"""
        # BRPOPLPUSH: atomik dequeue + processing kuyruğuna ekle
        item = self.redis.brpoplpush(
            self.queue_key,
            self.processing_key,
            timeout=5
        )
        
        if item:
            return json.loads(item)
        return None
    
    def mark_completed(self, email_job):
        """E-posta gönderimini tamamlandı işaretle"""
        self.redis.lrem(
            self.processing_key,
            1,
            json.dumps(email_job)
        )
    
    def mark_failed(self, email_job, error):
        """Başarısız e-postayı kaydet"""
        email_job['error'] = str(error)
        email_job['failed_at'] = datetime.utcnow().isoformat()
        
        self.redis.lpush(
            self.failed_key,
            json.dumps(email_job)
        )
        
        self.redis.lrem(
            self.processing_key,
            1,
            json.dumps(email_job)
        )
    
    def retry_failed(self, email_job, max_attempts=3):
        """Başarısız e-postayı tekrar dene"""
        email_job['attempts'] += 1
        
        if email_job['attempts'] < max_attempts:
            # Tekrar kuyruğa ekle
            self.redis.lpush(
                self.queue_key,
                json.dumps(email_job)
            )
            self.redis.lrem(
                self.processing_key,
                1,
                json.dumps(email_job)
            )
            return True
        else:
            # Max deneme aşıldı, failed'a taşı
            self.mark_failed(email_job, "Max attempts exceeded")
            return False

# Worker
import time

def email_worker(queue, sender):
    """E-posta gönderme worker'ı"""
    logger.info("Email worker started")
    
    while True:
        try:
            # Kuyruktan e-posta al
            email_job = queue.dequeue()
            
            if email_job:
                logger.info(f"Processing email: {email_job['id']}")
                
                try:
                    # E-postayı gönder
                    email_data = email_job['data']
                    msg = EmailMessage()
                    msg['Subject'] = email_data['subject']
                    msg['From'] = email_data['from']
                    msg['To'] = email_data['to']
                    msg.set_content(email_data['body'])
                    
                    sender.send_email(msg)
                    
                    # Başarılı, completed işaretle
                    queue.mark_completed(email_job)
                    logger.info(f"Email sent: {email_job['id']}")
                    
                except Exception as e:
                    logger.error(f"Failed to send email: {str(e)}")
                    
                    # Retry veya failed'a taşı
                    if not queue.retry_failed(email_job):
                        logger.error(
                            f"Email permanently failed: {email_job['id']}"
                        )
            
            time.sleep(0.1)  # Rate limiting
            
        except KeyboardInterrupt:
            logger.info("Worker stopped by user")
            break
        
        except Exception as e:
            logger.error(f"Worker error: {str(e)}")
            time.sleep(1)

2. Email Analytics ve Tracking

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from datetime import datetime
import hashlib

class EmailTracker:
    """E-posta tracking ve analytics"""
    
    def __init__(self, database):
        self.db = database
    
    def generate_tracking_pixel(self, email_id):
        """Tracking pixel URL oluştur"""
        token = hashlib.md5(
            f"{email_id}{datetime.utcnow()}".encode()
        ).hexdigest()
        
        return f"https://track.company.com/pixel/{email_id}/{token}.gif"
    
    def generate_click_tracking_url(self, email_id, original_url):
        """Link tracking URL oluştur"""
        token = hashlib.md5(
            f"{email_id}{original_url}".encode()
        ).hexdigest()
        
        return f"https://track.company.com/click/{email_id}/{token}"
    
    def add_tracking_to_html(self, email_id, html_content):
        """HTML içeriğe tracking ekle"""
        # Tracking pixel ekle
        pixel_url = self.generate_tracking_pixel(email_id)
        tracking_pixel = f'<img src="{pixel_url}" width="1" height="1" />'
        
        # HTML'in sonuna ekle
        if '</body>' in html_content:
            html_content = html_content.replace(
                '</body>',
                f'{tracking_pixel}</body>'
            )
        else:
            html_content += tracking_pixel
        
        # Link'leri tracking ile değiştir
        # (Gerçek implementasyonda BeautifulSoup kullan)
        
        return html_content
    
    def record_open(self, email_id, user_agent, ip_address):
        """E-posta açılışını kaydet"""
        self.db.execute("""
            INSERT INTO email_opens (
                email_id, opened_at, user_agent, ip_address
            ) VALUES (?, ?, ?, ?)
        """, (email_id, datetime.utcnow(), user_agent, ip_address))
    
    def record_click(self, email_id, url, user_agent, ip_address):
        """Link tıklamasını kaydet"""
        self.db.execute("""
            INSERT INTO email_clicks (
                email_id, url, clicked_at, user_agent, ip_address
            ) VALUES (?, ?, ?, ?, ?)
        """, (email_id, url, datetime.utcnow(), user_agent, ip_address))
    
    def get_analytics(self, email_id):
        """E-posta analytics getir"""
        return {
            'sent_at': self.db.query(
                "SELECT sent_at FROM emails WHERE id = ?",
                (email_id,)
            ),
            'opens': self.db.query(
                "SELECT COUNT(*) FROM email_opens WHERE email_id = ?",
                (email_id,)
            ),
            'unique_opens': self.db.query(
                "SELECT COUNT(DISTINCT ip_address) FROM email_opens "
                "WHERE email_id = ?",
                (email_id,)
            ),
            'clicks': self.db.query(
                "SELECT COUNT(*) FROM email_clicks WHERE email_id = ?",
                (email_id,)
            ),
            'click_rate': self.db.query(
                "SELECT (COUNT(DISTINCT email_clicks.email_id) * 100.0 / "
                "COUNT(DISTINCT email_opens.email_id)) "
                "FROM email_clicks, email_opens "
                "WHERE email_clicks.email_id = ? AND email_opens.email_id = ?",
                (email_id, email_id)
            )
        }

E-posta tracking için 1x1 piksel görsel kullanımı yaygındır. Ancak bazı e-posta istemcileri görselleri otomatik yüklemez, bu durumda açılış oranları gerçek değerin altında olabilir.

Sonuç

Python ile e-posta otomasyonu, modern uygulamaların vazgeçilmez bir parçasıdır. SMTP protokolü, HTML template’leri, toplu e-posta, error handling ve email service provider entegrasyonları ile profesyonel e-posta sistemleri oluşturabilirsiniz.

Bu yazıda ele aldığımız konular:

  • SMTP protokolü ve smtplib kullanımı
  • HTML e-posta ve Jinja2 template rendering
  • Ek dosya (attachment) gönderimi
  • Toplu e-posta gönderimi ve rate limiting
  • SendGrid ve Mailgun entegrasyonları
  • Error handling ve retry logic
  • Email queue sistemi (Redis)
  • Email tracking ve analytics
  • Production best practices

Başarılı bir e-posta otomasyonu sistemi, reliability, deliverability ve analytics ile kullanıcı deneyimini artırır.

Kaynaklar:

Bu gönderi CC BY 4.0 lisansı altındadır.