delay()

Let's say you want to boil an egg.

You wait till the water boils and put the egg in.
You look on the clock and remember the start time that you put it in
You keep on looking on the clock
Once the required time (delay) has passed, you take the egg out

void loop()
{
  if water boils
  {
    execute 4 minutes
  }
}

void execute(unsigned long delayTime)
{
  if egg not yet in water
  {
    put egg in water
    remember the start time
  }
  else  if current time - start start >= delay time  // keep on checking time
  {
    take egg out
  }
  else
  {
    do nothing
  }
}

Translated to your situation

void execute(unsigned long delayTime)
{
  static unsigned long startTime = 0;

  // if egg not yet in water
  if (startTime == 0)
  {
    // put egg in water
    digitalWrite(relay, HIGH);
    // remember start time
    startTime = millis();
  }
  // else if it's time to take it out
  else if (millis() - startTime >= delayTime)
  {
    // take it out
    digitalWrite(relay, LOW);
    // reset start time (for the next egg)
    startTime = 0;
  }
  else
  {
  }
}

Now it can also be useful to know when the boiling of the egg is finished; for that we can let the function return a status

/*
  switch on relay for given duration
  returns true if the duration has passed, else false

  note: none-blocking; call repeatedly
*/
bool execute(unsigned long delayTime)
{
  static unsigned long startTime = 0;

  if (startTime == 0)
  {
    digitalWrite(relay, HIGH);
    startTime = millis();
    return false;
  }
  else if (millis() - startTime >= delayTime)
  {
    digitalWrite(relay, LOW);
    startTime = 0;
    return true;
  }
  else
  {
    return false;
  }
}

And in loop we can call it e.g. like this

void loop()
{
  if(execute(1000) == true)
  {
    Serial.println("execute finished");
    // delay added to see led flash
    delay(500);
  }
}