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

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:

  1. We blocked the user’s account from our system immediately
  2. We marked all emails triggered by the attacker emails as “failed”
  3. 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.

Oğuz Bayat
Want to see Fabrikatör in action?
Get a 30-minute free demo and see how Fabrikatör can improve your inventory operations.
Get a Demo

free newsletter

Newsletter Signup

Get the best in inventory management & Shopify in your inbox
Thank you!
Your submission has been received!
Oops! Something went wrong while submitting the form.

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

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:

  1. We blocked the user’s account from our system immediately
  2. We marked all emails triggered by the attacker emails as “failed”
  3. 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.

Want to see Fabrikatör in action?
Get a 30-minute free demo and see how Fabrikatör can improve your inventory operations.
GET a Demo

free newsletter

Newsletter Signup

Get the best in inventory management & Shopify in your inbox
Thank you!
Your submission has been received!
Oops! Something went wrong while submitting the form.

Technology

View More Posts

Want to see Fabrikatör in action?

Get a 30-minute free demo and see how Fabrikatör can improve your inventory operations.
GET a Demo
By clicking “Accept”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.