How to embed multiple images in HTML emails using Python and Mailgun API?

I’m trying to figure out how to send HTML emails with inline images using Python and the Mailgun API. My current setup uses Flask, Jinja2, and the requests library. Here’s what I’ve got so far:

def send_email(recipient, sender, subject, text, html_content, image_files):
    response = requests.post(
        f'https://api.mailgun.net/v2/{config.MAILGUN_DOMAIN}/messages',
        auth=('api', config.MAILGUN_KEY),
        data={
            'from': sender,
            'to': recipient,
            'subject': subject,
            'text': text,
            'html': html_content,
            'inline': image_files
        }
    )
    return response

def prepare_email(user_email):
    sender = '[email protected]'
    subject = 'Welcome to Our Service'
    text = ''
    username = user_email.split('@')[0]
    html_content = render_template('welcome_email.html', username=username)
    
    image_files = [
        open('static/logo.png', 'rb'),
        open('static/banner.jpg', 'rb')
    ]
    
    send_email(user_email, sender, subject, text, html_content, image_files)

The email sends fine, but the images don’t show up. In my HTML template, I’m using <img src="cid:logo.png"> to reference the images. Am I missing something? Should I be handling the image files differently? Any help would be appreciated!

I’ve found that using the ‘inline’ parameter in Mailgun can be tricky. Instead, try attaching the images and referencing them with Content-ID headers. Here’s a modified version of your code that should work:

def send_email(recipient, sender, subject, text, html_content, image_files):
    files = []
    for img in image_files:
        files.append(('inline', (img.name, img, 'image/png')))

    response = requests.post(
        f'https://api.mailgun.net/v2/{config.MAILGUN_DOMAIN}/messages',
        auth=('api', config.MAILGUN_KEY),
        files=files,
        data={
            'from': sender,
            'to': recipient,
            'subject': subject,
            'text': text,
            'html': html_content
        }
    )
    return response

In your HTML template, reference the images like this: <img src=\"cid:logo.png\">. This approach has worked well for me in the past. Let me know if you need any clarification.

As someone who’s worked extensively with email APIs, I can tell you that handling inline images can be a bit tricky. One approach that’s worked well for me is using the requests-toolbelt library. It simplifies the process of creating multipart encoded requests, which is exactly what you need for inline images.

Here’s a snippet that might help:

from requests_toolbelt import MultipartEncoder

def send_email(recipient, sender, subject, text, html_content, image_files):
    data = MultipartEncoder(
        fields={
            'from': sender,
            'to': recipient,
            'subject': subject,
            'text': text,
            'html': html_content,
            'inline': [(f.name, f.read(), 'image/png') for f in image_files]
        }
    )
    
    response = requests.post(
        f'https://api.mailgun.net/v3/{config.MAILGUN_DOMAIN}/messages',
        auth=('api', config.MAILGUN_KEY),
        data=data,
        headers={'Content-Type': data.content_type}
    )
    return response

This approach has consistently worked for me across different email clients. Give it a shot and let me know if you run into any issues.

hey alexj, i’ve dealt with this before. try encoding ur images as base64 and embedding them directly in the html. something like:

import base64

with open('static/logo.png', 'rb') as f:
    img_data = base64.b64encode(f.read()).decode()

html_content = f'<img src=\"data:image/png;base64,{img_data}\">'

this way u don’t need to mess with CID stuff. hope it helps!