CSV attachment not working with Mailgun in Rails application

I’m trying to create a CSV file and attach it to an email using Rails and Mailgun, but I’m running into issues. The CSV content shows up as plain text in the email body instead of being a proper attachment.

Here’s how I generate the CSV data:

report_data = CSV.generate(col_sep: ",") do |csv|
    csv << ["created_at", "user_name", "user_email", "content"]
    
    entries.each do |entry|
      csv << [entry.created_at, entry.user_name, entry.user_email, entry.content]
    end
end

Then I try to send it through my mailer:

def send_report_csv(csv_content, recipient_email)
   mail.attachments["report.csv"] = csv_content
   mail(to: recipient_email, subject: 'Data Report')
end

I call it like this:

ReportMailer.send_report_csv(report_data, user_email).deliver

The weird thing is that when I test locally using sendmail, the attachment works perfectly. But when deployed on Heroku with Mailgun, the CSV data appears as text in the email body instead of an attachment file.

Has anyone experienced this issue before? What could be causing Mailgun to handle attachments differently?

This is an encoding issue between Rails and Mailgun’s API. Had the same problem last year switching from SendGrid to Mailgun. Mailgun expects attachments to be properly encoded when you’re using their REST API through Rails. You need to set the encoding explicitly for the CSV content. Try this: ruby def send_report_csv(csv_content, recipient_email) mail.attachments["report.csv"] = { mime_type: 'application/csv', content: csv_content, encoding: 'Base64' } mail(to: recipient_email, subject: 'Data Report') end Or force the encoding when generating the CSV by adding .force_encoding('UTF-8') to your CSV.generate block result. It works locally with sendmail because sendmail handles MIME encoding differently than Mailgun’s API. Mailgun’s stricter about multipart message formatting. Setting both mime_type and encoding explicitly fixes most Mailgun attachment issues in production.

Your problem is with how Mailgun handles attachment content-type headers. I hit this exact issue when moving from local dev to Mailgun production. Rails generates CSV as a string, but Mailgun needs explicit content disposition headers to treat it as an attachment instead of inline content. When you just pass the string content, Mailgun thinks it’s text for the email body. Here’s what fixed it: ruby def send_report_csv(csv_content, recipient_email) mail.attachments["report.csv"] = { mime_type: 'text/csv', content: csv_content, disposition: 'attachment' } mail(to: recipient_email, subject: 'Data Report') end The key is explicitly setting disposition: 'attachment'. This tells Mailgun to treat the CSV as a downloadable file instead of inline content. Without this, Mailgun defaults to inline for text-based content types. Sendmail works locally because it handles multipart messages differently, but Mailgun’s API needs explicit instructions for each email part.

I’ve hit this exact issue multiple times. Rails ActionMailer screws up MIME types for CSV attachments with certain email services.

Try setting the MIME type explicitly:

mail.attachments["report.csv"] = {
  mime_type: 'text/csv',
  content: csv_content
}

If that doesn’t fix it, it’s probably Mailgun mangling the multipart message from Rails.

Honestly? I’d ditch the Rails email complexity entirely. I use Latenode for CSV email reports now - no more weird Rails/Mailgun integration headaches.

With Latenode, I built a simple workflow that pulls data from my database, generates the CSV properly, and sends it through any email service. No ActionMailer layer to mess things up. No more debugging MIME types or wondering why attachments work locally but break in production.

The automation handles everything from data extraction to delivery. Zero Rails email quirks to deal with.

Been there. Mailgun’s API treats your CSV string as email body content instead of an attachment. Rails ActionMailer creates multipart messages that work with simple SMTP servers, but Mailgun’s API has stricter parsing rules.

I ditched the MIME types and encoding header mess and moved all my CSV reporting to Latenode. Much cleaner.

Set up a workflow that connects directly to my Rails database, pulls report data, generates properly formatted CSVs, and emails them. No more ActionMailer attachment headaches or things working locally but breaking in production.

The workflow handles everything: database query, CSV generation, email delivery with proper attachments. Skips the Rails email layer entirely.

10 minutes to set up and works consistently across all email providers. Way better than debugging Rails multipart message formatting.

same issue here. mailgun’s smtp and api handle attachments completely different. just switch to smtp in your rails mailer config - the api screws up multipart boundaries but smtp plays nice with actionmailer’s attachment system. just swap your mailgun config from api to smtp and ur done. no code changes needed.