Security and Rate Limiting: A Real-World Case in an Email Sending System

In today’s software world, security and rate limiting are not just “nice to have” features — they are must-haves. In this post, I’ll walk you through a real case we experienced in our email sending feature, how we detected a problem, and the steps we took to solve it.
What is Purchase Order Sharing?
Our app allows users to send purchase orders (POs) directly to their suppliers via email. This feature streamlines procurement communication by using customizable, pre-defined templates.
For example, a user can select a purchase order, choose a supplier, and send the email with just one click — all from within the app interface.
📘 Learn more about how to share a purchase order with a supplier.

How We Noticed the Issue
On March 28, 2025, we detected an unusually high email sending traffic in our system. After investigating, we realized that one of our new trial user accounts (the attacker) was abusing the Purchase Order sharing feature of our app.
This user had likely written a script to automate the frontend and started sending phishing-like emails to various recipients.
In total, there were 4,891 email send attempts in just one hour, and 4,266 of them were still in the queue. This excessive traffic caused us to exceed Mailgun’s hourly sending limit of 100 emails — which, unfortunately, also affected our legitimate users by blocking their emails from being sent.
The Attacker and Their Tactics
Upon investigation, we discovered that the abuse originated from a new trial user account. Although the associated Shopify store appeared to be a development store with only a few test products, the attacker was using a paid version of Shopify. This indicates a deliberate attempt to exploit apps within the Shopify ecosystem to send spam or phishing emails.
The attacker automated the Purchase Order sharing feature to send a high volume of unsolicited emails, aiming to bypass standard email security measures.
This method of abuse is not isolated; other developers have reported similar incidents in the Shopify Community forums. For instance, users have shared experiences of receiving fraudulent emails falsely claiming to raise security concerns about their Shopify stores (https://help.shopify.com/en/manual/privacy-and-security/account-security/phishing).
Emergency Actions
Once we identified the problem, we took the following steps:
- We blocked the user’s account from our system immediately
- We marked all emails triggered by the attacker emails as “failed”
- We contacted Mailgun and reactivated our account
Permanent Solution: Rate Limiting
To prevent this from happening again, we added a rate limiting mechanism to our system. Each company can have its own custom limit configuration, which we store in the database and use during email sending
1. Database Update
We added a new field to the companies
table to allow custom limits per company:
class AddSharePoLimitToCompanies < ActiveRecord::Migration
def change
add_column :companies, :share_po_email_limit, :jsonb, default: {}
end
end
2. Limit Check Before Sending
Before sending emails, we now check whether the company has reached its limits:
def within_rate_limits?(company, email_count)
hourly_limit = company.hourly_po_share_limit
daily_limit = company.daily_po_share_limit
keys = redis_keys(company)
hourly_count = REDIS.get(keys[:hourly]).to_i
daily_count = REDIS.get(keys[:daily]).to_i
hourly_count + email_count <= hourly_limit &&
daily_count + email_count <= daily_limit
end
3. Email Counters in Redis
We store email counters in Redis for fast and reliable tracking:
def increment_email_counters(company, email_count)
keys = redis_keys(company)
REDIS.multi do |multi|
multi.incrby(keys[:hourly], email_count)
multi.incrby(keys[:daily], email_count)
multi.expire(keys[:hourly], 1.hour.to_i)
multi.expire(keys[:daily], 1.day.to_i)
end
end
What We Learned
- Security is not just about firewalls: Rate limiting helps both with performance and protecting against abuse.
- Flexibility matters: Custom limits per company allowed us to adapt to different use cases.
- Monitoring is essential: Early detection of abnormal behavior saves time and prevents damage.
Our Next Steps
Rate limiting is a strong line of defense, but it's not enough on its own. To make our system more proactive and intelligent, we’re planning to take the following steps:
- LLM-powered content analysis:We plan to analyze email content using Large Language Models (LLMs) before sending. These models will scan email bodies in real-time and detect spam, phishing, or suspicious content. If a message looks harmful or risky, the system will automatically block it from being sent.Here’s how we’ll approach it:
- Represent the email body using embeddings and compare it with pre-labeled examples
- Optionally, use prompt-based queries like “Is this a phishing email?” to get a direct classification from the model
- If the model returns a low confidence score, the message will be blocked
- Monitoring and alert system:
- We plan to build a dashboard to monitor suspicious activity and also send Slack notifications to our internal team in real time. This way, the team can take immediate action when needed.
With these improvements, our goal is to move beyond manual checks and build a smarter, scalable security layer.
🔒 Data Security Notice
We want to reassure our users and partners that no user data or company data was compromised during this incident. Our databases remained secure at all times, and the issue was isolated to the misuse of our email sending feature. We acted swiftly to contain the problem and implemented long-term safeguards to prevent similar abuse in the future. This article was written to demonstrate our transparency and the utmost care we take in safeguarding security at Fabrikatör.
🚀 Join Our Tech Team
We're a small, focused, and passionate team on a mission to build world-class supply chain software for eCommerce brands. If you’re excited about solving real-world problems at scale and working on a product that truly matters, we’d love to hear from you. Check out our open roles here.