I’m working on a Rails app that processes incoming emails through Mailgun. The setup is running on Heroku and I can see that emails are triggering my webhook endpoint correctly.
The problem is that while new records are being created in my Email model, all the fields remain empty. My database schema has the right columns like sender, title, and files, but the actual email data isn’t getting saved.
I need help figuring out how to extract data from the webhook payload and map it to my model attributes. Right now I want to capture these fields:
Sender email address
Email title
File attachments
Here’s my current controller code:
def create
@email = Email.new(params[:email])
respond_to do |format|
if @email.save
format.html { redirect_to @email, notice: 'Email saved successfully.' }
format.json { render json: @email, status: :created, location: @email }
else
format.html { render action: "new" }
format.json { render json: @email.errors, status: :unprocessable_entity }
end
end
end
I can see the webhook requests in my Heroku logs but I’m not sure how to properly parse the incoming data. What’s the correct way to handle this?
Your problem is how you’re accessing the webhook data. Mailgun doesn’t send data as nested email parameters - it sends them as form parameters.
Grab the data straight from the params hash:
def create
email_data = {
sender: params['sender'],
title: params['subject'],
# For attachments, you'll need to handle the attachment-count and attachment-x parameters
}
@email = Email.new(email_data)
if @email.save
render json: { status: 'ok' }
else
render json: @email.errors, status: :unprocessable_entity
end
end
Mailgun sends attachments as attachment-count and individual attachment-1, attachment-2 parameters.
That said, webhook parsing gets messy really fast. Been there, done that. I switched to automation and haven’t looked back.
Now I use Latenode to catch Mailgun webhooks, parse everything cleanly, then send it to Rails in exactly the format I want. You can set up conditional logic for different email types, handle attachments properly, even do preprocessing.
Way cleaner than stuffing all that logic into your controller. Plus you get retry logic and error handling built in.
The problem’s definitely how you’re handling the webhook payload. Mailgun doesn’t nest webhook data under an ‘email’ key like you’re expecting with params[:email] - it sends everything as top-level form parameters.
I hit this exact issue 6 months ago. First thing - log the entire params hash to see what Mailgun’s actually sending. Throw Rails.logger.info params.inspect at the top of your create method and check your Heroku logs.
You’ll probably find the data under keys like ‘sender’, ‘subject’, ‘body-plain’, etc. For attachments, Mailgun sends ‘attachment-count’ then individual ones as ‘attachment-1’, ‘attachment-2’ with matching ‘attachment-1-name’ parameters.
Big gotcha: Mailgun includes signature verification data in the webhook. You need to verify authenticity using the timestamp and token parameters before processing anything. Skip this and you’re wide open to spoofed calls.
Also add error handling around email creation - webhook endpoints can get hammered multiple times if they don’t return a 200 status fast enough.
Been wrestling with email processing for years - handling webhooks directly in Rails sucks. Yeah, everyone’s right about parameter mapping, but there’s a bigger issue.
You’re building something fragile. What happens when Mailgun changes their format? Or you need multiple providers? Your controller turns into conditional parsing hell.
Hit this exact problem at my last job. Started with direct webhooks and it became a nightmare. Different events, attachments, retries, error handling - everything piles up.
What actually works long-term? Put an automation layer between Mailgun and Rails. I use Latenode to catch webhooks, normalize Mailgun’s messy data, handle attachments properly, then send clean JSON to my endpoint.
Now my controller just does Email.create(params.require(:email).permit(:sender, :title, :attachments)) and it works. Latenode handles webhook complexity, retries failures, and I can add filtering without touching Rails.
Way more reliable than parsing payloads manually. Plus you get monitoring and logs.
Had the same webhook parsing headaches with Mailgun on a client project. The tricky part isn’t just mapping parameters - it’s dealing with how Mailgun sends completely different data structures for each event type. Start with proper logging so you can see what’s actually hitting your endpoint. Then pull the parsing logic out of your controller into a service class. Something like MailgunWebhookProcessor.new(params).process keeps it cleaner. Attachments are annoying - Mailgun sends them as multipart data or base64 depending on file size. You’ll need to handle both. Bounced emails and delivery failures hit the same endpoint but use totally different parameter structures, so watch for that. One lifesaver: implement idempotency checks with Mailgun’s message-id parameter. These webhooks can fire multiple times for the same email. Cache processed message IDs for 24+ hours or you’ll get duplicate records.
Had this exact headache setting up email processing last year. Your parameter handling’s the problem - Mailgun webhooks don’t structure data like Rails forms do. Your controller’s looking for params[:email] but Mailgun sends webhook data as flat form parameters. You need to manually extract and map each field from the raw params. Here’s what works: ruby def create email_params = { sender: params['From'], title: params['Subject'], body: params['body-plain'] } @email = Email.new(email_params) if @email.save head :ok else head :unprocessable_entity end end Mailgun uses ‘From’ and ‘Subject’ as parameter names, not ‘sender’ and ‘title’. Check their webhook docs for exact field names. For attachments, you’ll need to iterate through the attachment parameters since they come as numbered pairs. Also add webhook signature verification to block unauthorized requests.
you’re missing strong params setup entirely. Mailgun sends webhook data, but Rails blocks unpermitted params by default. add this to your controller: params.permit(:sender, :subject, 'body-plain', 'attachment-count') and map them to your model fields. also check your routes - webhook endpoints need skip_before_action :verify_authenticity_token to bypass CSRF protection.