diff --git a/config/schema/contact_storage.schema.yml b/config/schema/contact_storage.schema.yml
index 92792ba..64b4162 100644
--- a/config/schema/contact_storage.schema.yml
+++ b/config/schema/contact_storage.schema.yml
@@ -11,3 +11,6 @@ contact.form.*.third_party.contact_storage:
     show_preview:
       type: boolean
       label: 'Show preview button'
+    maximum_submissions_user:
+      type: integer
+      label: 'Maximum submission limit per user'
diff --git a/contact_storage.module b/contact_storage.module
index 368f6f0..94d5d94 100644
--- a/contact_storage.module
+++ b/contact_storage.module
@@ -44,6 +44,12 @@ function contact_storage_form_contact_form_form_alter(&$form, FormStateInterface
     '#description' => t('Show the preview button?'),
     '#default_value' => $contact_form->getThirdPartySetting('contact_storage', 'show_preview', TRUE),
   ];
+  $form['contact_storage_maximum_submissions_user'] = [
+    '#type' => 'textfield',
+    '#title' => t('Maximum submissions'),
+    '#description' => t('The maximum number of times, per user, the form can be submitted (0 for unlimited).'),
+    '#default_value' => $contact_form->getThirdPartySetting('contact_storage', 'maximum_submissions_user', 0),
+  ];
   $form['#entity_builders'][] = 'contact_storage_contact_form_form_builder';
 }
 /**
@@ -55,6 +61,7 @@ function contact_storage_contact_form_form_builder($entity_type, ContactFormInte
   $contact_form->setThirdPartySetting('contact_storage', 'redirect_uri', $form_state->getValue('contact_storage_uri'));
   $contact_form->setThirdPartySetting('contact_storage', 'submit_text', $form_state->getValue('contact_storage_submit_text'));
   $contact_form->setThirdPartySetting('contact_storage', 'show_preview', $form_state->getValue('contact_storage_preview'));
+  $contact_form->setThirdPartySetting('contact_storage', 'maximum_submissions_user', $form_state->getValue('contact_storage_maximum_submissions_user'));
 }
 
 /**
@@ -77,6 +84,54 @@ function contact_storage_form_contact_message_form_alter(&$form, &$form_state, $
       $form['actions']['preview']['#access'] = FALSE;
     }
   }
+
+  // Check if the current user has reached the form's maximum submission limit.
+  $maximum_submissions_user = $contact_form->getThirdPartySetting('contact_storage', 'maximum_submissions_user', 0);
+  if (($maximum_submissions_user !== 0) && contact_storage_maximum_submissions_user($contact_form) >= $maximum_submissions_user) {
+    // Sets the error message.
+    $form['maximum_submissions_error'] = array(
+      '#type' => 'container',
+      '#markup' => t('You have reached the maximum submission limit of @limit for this form.', ['@limit' => $maximum_submissions_user]),
+      '#attributes' => array(
+        'class' => array('messages', 'messages--error'),
+      ),
+      '#weight' => -100,
+    );
+    // Remove the submit and preview buttons.
+    $form['actions']['submit']['#access'] = FALSE;
+    $form['actions']['preview']['#access'] = FALSE;
+  }
+}
+
+/**
+ * Returns the number of times the current user has submitted the specified
+ * form.
+ *
+ * @param Drupal\contact\ContactFormInterface $contact_form
+ *   The contact_form entity.
+ *
+ * @return int
+ *   The number of times the current user has submitted the specified form.
+ */
+function contact_storage_maximum_submissions_user(ContactFormInterface $contact_form) {
+  $account = \Drupal::currentUser();
+
+  if ($account->isAnonymous()) {
+    // Anonymous user, limit per submission with the same IP address.
+    $ip_address = \Drupal::request()->getClientIp();
+    $query = \Drupal::entityQuery('contact_message')
+      ->condition('contact_form', $contact_form->id())
+      ->condition('ip_address', $ip_address)
+      ->condition('uid', $account->id());
+    return count($query->execute());
+  }
+  else {
+    // Limit per submission with the same uid.
+    $query = \Drupal::entityQuery('contact_message')
+      ->condition('contact_form', $contact_form->id())
+      ->condition('uid', $account->id());
+    return count($query->execute());
+  }
 }
 
 /**
@@ -114,10 +169,41 @@ function contact_storage_entity_base_field_info(EntityTypeInterface $entity_type
       ->setTranslatable(TRUE)
       ->setReadOnly(TRUE);
 
+    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('User ID'))
+      ->setDescription(t('The user ID.'))
+      ->setSetting('target_type', 'contact_form')
+      ->setDefaultValueCallback('contact_storage_contact_message_default_uid');
+
+    $fields['ip_address'] = BaseFieldDefinition::create('string')
+      ->setLabel(t('IP address'))
+      ->setDescription(t('The IP address of the submitter.'))
+      ->setDefaultValueCallback('contact_storage_contact_message_default_ip_address');
+
     return $fields;
   }
 }
 
+/**
+ * Returns the user id, which is stored in the contact_message entity.
+ *
+ * @return int
+ *   The user ID.
+ *
+ */
+function contact_storage_contact_message_default_uid() {
+  return \Drupal::currentUser()->id();
+}
+
+/**
+ * Returns the user IP address, which is stored in the contact_message entity.
+ *
+ * @return int
+ *   The client IP address.
+ */
+function contact_storage_contact_message_default_ip_address() {
+  return \Drupal::request()->getClientIp();
+}
 
 /**
  * Implements hook_entity_base_field_info_alter().
@@ -131,6 +217,9 @@ function contact_storage_entity_base_field_info_alter(&$fields, EntityTypeInterf
       $fields[$field_name]->setDisplayOptions('view', array('weight' => $i));
       $i++;
     }
+    // Add a validation constraint to prevent form submission if the limit is
+    // reached.
+    $fields['message']->addConstraint('ConstactStorageMaximumSubmissions', []);
   }
 }
 
diff --git a/src/Plugin/Validation/Constraint/ConstactStorageMaximumSubmissionsConstraint.php b/src/Plugin/Validation/Constraint/ConstactStorageMaximumSubmissionsConstraint.php
new file mode 100644
index 0000000..e1be65c
--- /dev/null
+++ b/src/Plugin/Validation/Constraint/ConstactStorageMaximumSubmissionsConstraint.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\contact_storage\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Verify that the form has not been submitted more times that the limit.
+ *
+ * @Constraint(
+ *   id = "ConstactStorageMaximumSubmissions",
+ *   label = @Translation("Maximum submission limit", context = "Validation"),
+ * )
+ */
+class ConstactStorageMaximumSubmissionsConstraint extends Constraint {
+
+  /**
+   * Message shown when the maximum submission limit has been reached.
+   *
+   * @var string
+   */
+  public $limitReached = 'You have reached the maximum submission limit of @limit for this form.';
+
+}
diff --git a/src/Plugin/Validation/Constraint/ConstactStorageMaximumSubmissionsConstraintValidator.php b/src/Plugin/Validation/Constraint/ConstactStorageMaximumSubmissionsConstraintValidator.php
new file mode 100644
index 0000000..f94ad44
--- /dev/null
+++ b/src/Plugin/Validation/Constraint/ConstactStorageMaximumSubmissionsConstraintValidator.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\contact_storage\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the maximum submission limit constraint.
+ */
+class ConstactStorageMaximumSubmissionsConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($entity, Constraint $constraint) {
+    // Check if the current user has reached the form's maximum submission limit.
+    $contact_form = $entity->getParent()->get('contact_form')->referencedEntities()[0];
+    $maximum_submissions_user = $contact_form->getThirdPartySetting('contact_storage', 'maximum_submissions_user', 0);
+    if (($maximum_submissions_user !== 0) && contact_storage_maximum_submissions_user($contact_form) >= $maximum_submissions_user) {
+      // Limit reached; can't submit the form.
+      $this->context->addViolation($constraint->limitReached, ['@limit' => $maximum_submissions_user]);
+    }
+  }
+
+}
diff --git a/src/Tests/ContactStorageTest.php b/src/Tests/ContactStorageTest.php
index 5b7c662..21076da 100644
--- a/src/Tests/ContactStorageTest.php
+++ b/src/Tests/ContactStorageTest.php
@@ -167,6 +167,28 @@ class ContactStorageTest extends ContactStorageTestBase {
     $this->drupalPostForm(NULL, $edit, t('Submit the form'));
     $form = ContactForm::load('test_id_2');
     $this->assertTrue($form->uuid());
+
+    // Create a new contact form with a maximum submission limit of 2.
+    $this->addContactForm('test_id_3', 'test_label', $mail, '', FALSE, ['contact_storage_maximum_submissions_user' => 2]);
+    $this->assertText(t('Contact form test_label has been added.'));
+
+    // Sends 2 messages with "Send yourself a copy" option activated, shouldn't
+    // reach the limit even if 2 messages are sent twice.
+    $this->drupalGet('contact/test_id_3');
+    $edit = [
+      'subject[0][value]' => 'Test subject',
+      'message[0][value]' => 'Test message',
+      'copy' => 'checked',
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Send message'));
+    $this->assertText(t('Your message has been sent.'));
+    $this->drupalGet('contact/test_id_3');
+    $this->drupalPostForm(NULL, $edit, t('Send message'));
+    $this->assertText(t('Your message has been sent.'));
+
+    // Try accessing the form after the limit has been reached.
+    $this->drupalGet('contact/test_id_3');
+    $this->assertText(t('You have reached the maximum submission limit of 2 for this form.'));
   }
 
 }
