Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

aarontharker's avatar

Email behaving strange

I have 2 emails that are setup using essentially identical code, 1 works fine and 1 doesn't. I can't see why! The code in the controller that triggers the emails are these, Working -

Mail::to($user)->send(new EmailVerification($user));

Error -

Mail::to($user)->send(new ForgottenPasswordVerification($user));
TypeError: Symfony\Component\Mime\Address::__construct(): Argument #1 ($address) must be of type string, null given, called in /app/vendor/laravel/framework/src/Illuminate/Mail/Message.php

In both cases, $user should be an eloquent model. The first is raised via a User::create, the second via a User::where()->first(). The actual mail classes are essentially identical, I copied the working one to the new one and changed the names to make sure!

0 likes
29 replies
aarontharker's avatar

working -

<?php

namespace App\Mail;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class EmailVerification extends Mailable
{
    use Queueable, SerializesModels;

    public $user;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->markdown('emails.user.emailverification');
    }
}

Error -

<?php

namespace App\Mail;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class ForgottenPasswordVerification extends Mailable
{
    use Queueable, SerializesModels;

    public $user;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->markdown('emails.user.emailverification');
    }
}
Sinnbeck's avatar

Can you show the code that sends the one that does not work. Also the code that sets $user

aarontharker's avatar

Working function

public function verifyEmail (Request $request)
    {

        $validated = $request->validate([
            'id'    => ['required', 'integer'],
            'code'  => ['required', 'integer'],
            'device_name'   => ['required', 'string'],
        ]);

        $user = User::find($validated['id']);

        if (! $user) {
            throw ValidationException::withMessages([
                'id' => ['Unknown user id.'],
            ]);
        } elseif ($user->verification_code != $validated['code']) {
            throw ValidationException::withMessages([
                'code' => ['Incorrect code entered.'],
            ]);
        }

        $user->email_verified_at = Carbon::now()->toDateTimeString();
        $user->verification_code = null;
        $user->save();

        $user->token = $user->createToken($request->device_name)->plainTextToken;
     
        return (new UserResource($user))
            ->response()
            ->setStatusCode(Response::HTTP_OK);

    }

Not working function

public function forgotPassword (Request $request)
    {
        $validated = $request->validate([
            'email'    => ['required', 'email'],
        ]);

        try {
            $user = User::where('email', $validated['email'])->first();
            $code = rand(1111,9999);
            $user->verification_code = $code;
            $user->save();

            Mail::to($user)->send(new ForgottenPasswordVerification($user));

            return response('Email sent', 200);
        } catch(e) {
            return response('Unknown email', 400);
        }
    }
Sinnbeck's avatar

@aarontharker Ok right after that line, do dd($user);. Do you see a fill user with email and everything?

aarontharker's avatar

I did a dd on the $user before it got sent to mail and it is definitely a model

App\Models\User {#1766 // app/Http/Controllers/Api/V1/AuthController.php:207
  #connection: "pgsql"
  +table: "users"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  +preventsLazyLoading: false
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #escapeWhenCastingToString: false
  #attributes: array:3 [
    "updated_at" => "2022-10-14 06:24:13"
    "verification_code" => 5147
    "id" => 13
  ]
  #original: array:3 [
    "updated_at" => "2022-10-14 06:24:13"
    "verification_code" => 5147
    "id" => 13
  ]
  #changes: array:2 [
    "updated_at" => "2022-10-14 06:24:13"
    "verification_code" => 5147
  ]
  #casts: array:1 [
    "deleted_at" => "datetime"
  ]
  #classCastCache: []
  #attributeCastCache: []
  #dates: array:5 [
    0 => "email_verified_at"
    1 => "membership_expiry"
    2 => "created_at"
    3 => "updated_at"
    4 => "deleted_at"
  ]
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #hidden: array:3 [
    0 => "remember_token"
    1 => "password"
    2 => "verification_code"
  ]
  #visible: []
  #fillable: array:10 [
    0 => "name"
    1 => "email"
    2 => "password"
    3 => "locale"
    4 => "address"
    5 => "city_id"
    6 => "membership_id"
    7 => "membership_expiry"
    8 => "device_key"
    9 => "verification_code"
  ]
  #guarded: array:1 [
    0 => "*"
  ]
  #rememberTokenName: "remember_token"
  +orderable: array:11 [
    0 => "id"
    1 => "name"
    2 => "email"
    3 => "email_verified_at"
    4 => "locale"
    5 => "address"
    6 => "city.city_name"
    7 => "city.country_name"
    8 => "membership.type"
    9 => "membership_expiry"
    10 => "device_key"
  ]
  +filterable: array:13 [
    0 => "id"
    1 => "name"
    2 => "email"
    3 => "email_verified_at"
    4 => "roles.title"
    5 => "locale"
    6 => "address"
    7 => "city.city_name"
    8 => "city.country_name"
    9 => "cuisines.name"
    10 => "membership.type"
    11 => "membership_expiry"
    12 => "device_key"
  ]
  #accessToken: null
  #forceDeleting: false
}
Sinnbeck's avatar

@aarontharker But there is no email on the model?

 #original: array:3 [
    "updated_at" => "2022-10-14 06:24:13"
    "verification_code" => 5147
    "id" => 13
  ]

So are you hiding it somehow? Some global scope that does not select it perhaps?

aarontharker's avatar

@sinnbeck Ok if I do a dd on the $user model in the other function it has 7 attributes in the array

Sinnbeck's avatar

@aarontharker Do you have multiple user models maybe? Is the import at the top the same? There is something we aren't seeing that is doing the difference, but I cannot say what.

aarontharker's avatar

This is the working $user

App\Models\User {#1753 // app/Http/Controllers/Api/V1/AuthController.php:129
  #connection: "pgsql"
  +table: "users"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  +preventsLazyLoading: false
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: true
  #escapeWhenCastingToString: false
  #attributes: array:7 [
    "name" => "Postman"
    "email" => "[email protected]"
    "password" => "y$XGe.DL0O8TRToQMGm3jsJexCVYOWy6U/A7yT.gNoQuznDJqQkOBzK"
    "verification_code" => 4545
    "updated_at" => "2022-10-14 06:32:35"
    "created_at" => "2022-10-14 06:32:35"
    "id" => 14
  ]
  #original: array:7 [
    "name" => "Postman"
    "email" => "[email protected]"
    "password" => "y$XGe.DL0O8TRToQMGm3jsJexCVYOWy6U/A7yT.gNoQuznDJqQkOBzK"
    "verification_code" => 4545
    "updated_at" => "2022-10-14 06:32:35"
    "created_at" => "2022-10-14 06:32:35"
    "id" => 14
  ]
  #changes: []
  #casts: array:1 [
    "deleted_at" => "datetime"
  ]
  #classCastCache: []
  #attributeCastCache: []
  #dates: array:5 [
    0 => "email_verified_at"
    1 => "membership_expiry"
    2 => "created_at"
    3 => "updated_at"
    4 => "deleted_at"
  ]
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #hidden: array:3 [
    0 => "remember_token"
    1 => "password"
    2 => "verification_code"
  ]
  #visible: []
  #fillable: array:10 [
    0 => "name"
    1 => "email"
    2 => "password"
    3 => "locale"
    4 => "address"
    5 => "city_id"
    6 => "membership_id"
    7 => "membership_expiry"
    8 => "device_key"
    9 => "verification_code"
  ]
  #guarded: array:1 [
    0 => "*"
  ]
  #rememberTokenName: "remember_token"
  +orderable: array:11 [
    0 => "id"
    1 => "name"
    2 => "email"
    3 => "email_verified_at"
    4 => "locale"
    5 => "address"
    6 => "city.city_name"
    7 => "city.country_name"
    8 => "membership.type"
    9 => "membership_expiry"
    10 => "device_key"
  ]
  +filterable: array:13 [
    0 => "id"
    1 => "name"
    2 => "email"
    3 => "email_verified_at"
    4 => "roles.title"
    5 => "locale"
    6 => "address"
    7 => "city.city_name"
    8 => "city.country_name"
    9 => "cuisines.name"
    10 => "membership.type"
    11 => "membership_expiry"
    12 => "device_key"
  ]
  #accessToken: null
  #forceDeleting: false
}
aarontharker's avatar

Both functions are in the same class so they have all the same Models. Is it something to do with LazyLoading?

Sinnbeck's avatar

@aarontharker Lazy loading is when load a relationship. This is not a relationship :)

Maybe you can show the complete controller if they are both in the same?

aarontharker's avatar

sure

<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use Carbon\Carbon;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use App\Mail\EmailVerification;
use App\Mail\ForgottenPasswordVerification;
use App\Http\Resources\Admin\UserResource;

/**
 * 
 * @group V1
 * @subgroup Auth
 * @subgroupDescription Actions for authenicating the user.
 * 
 */

class AuthController extends Controller
{
    /**
     * 
     * @unauthenticated
     * API Login Method
     * 
     * @response {
     *      "data": {
     *      "id": 22,
     *       "name": "Aaron T Harker",
     *       "email": "[email protected]",
     *       "locale": "en",
     *       "email_verified_at": "2022-09-26 05:59:43",
     *       "created_at": "2022-09-26 05:59:43",
     *       "updated_at": "2022-09-26 05:59:43",
     *       "deleted_at": null,
     *       "address": "16 Somewhere St",
     *       "city_id": 25,
     *       "state_id": 11,
     *       "country_id": 66,
     *       "membership_id": 2,
     *       "membership_expiry": "2025-09-26 05:59:43",
     *       "referrer_id": 1,
     *       "token": "3|GbwQj6eQQZX2FGSGbgHGMyDkLEi9LzOG3FQRIYHr"
     *      }
     *   }
     * 
     * 
     */
    
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required',
            'device_name' => 'required',
        ]);
     
        $user = User::where('email', $request->email)->first();
     
        if (! $user || ! Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['The provided credentials are incorrect.'],
            ]);
        }

        if (! $user->email_verified_at) {
            throw ValidationException::withMessages([
                'email' => ['Your email address has not been verified, please check your email(include spam folders).'],
            ]);
        }

        $user->token = $user->createToken($request->device_name)->plainTextToken;
     
        return (new UserResource($user))
            ->response()
            ->setStatusCode(Response::HTTP_OK);
    }

    /**
     * 
     * @unauthenticated
     * API Register Method
     * 
     * @response {
     *   "data": {
     *   "name": "Aaron T Harker",
     *   "email": "[email protected]",
     *   "updated_at": "2022-09-26 05:59:43",
     *   "created_at": "2022-09-26 05:59:43",
     *   "id": 22
     *  }
     *}
     *
     * @bodyParam name required string The name of the user. Example John Doe
     * @bodyParam email required string The user email must be unique. Example [email protected]
     * @bodyParam password required string The password must be between 8 and 16 characters, containing at least 1 number, 1 uppercase letter, 1 lowercase letter, and 1 special character with no spaces. Example P@ssw0rd!
     *
     */

    public function register(Request $request)
    {
        $validated = $request->validate([
            'name'     => ['required', 'string', 'max:255'],
            'email'    => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'regex:/^(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[^\w\d\s:])([^\s]){8,16}$/'],
        ]);

        $message = [
            'regex' => 'The :attribute must be between 8 and 16 characters, containing at least 1 number, 1 uppercase letter, 1 lowercase letter, and 1 special character with no spaces.',
        ];

        $code = rand(1111,9999);

        $user = User::create([
            'name'     => $validated['name'],
            'email'    => $validated['email'],
            'password' => Hash::make($validated['password']),
            'verification_code' => $code,
        ]);

        $user->roles()->sync(2);

        dd($user);

        Mail::to($user)->send(new EmailVerification($user));

        return (new UserResource($user))
            ->response()
            ->setStatusCode(Response::HTTP_CREATED);
    }

    /**
     * 
     * @unauthenticated
     * API Verify Email Method
     * 
     * @response {
     *   "data": {
     *   "email_verified_at": "25-09-2022 12:19:56",
     *   "updated_at": "2022-09-25 12:19:56",
     *   "id": 21,
     *   "token": "2|Cu54MdCPRsPwtLny17UicB7WN4Me83TeW02Gth1W"
     *  }
     *}
     * 
     *
     */

    public function verifyEmail (Request $request)
    {

        $validated = $request->validate([
            'id'    => ['required', 'integer'],
            'code'  => ['required', 'integer'],
            'device_name'   => ['required', 'string'],
        ]);

        $user = User::find($validated['id']);

        if (! $user) {
            throw ValidationException::withMessages([
                'id' => ['Unknown user id.'],
            ]);
        } elseif ($user->verification_code != $validated['code']) {
            throw ValidationException::withMessages([
                'code' => ['Incorrect code entered.'],
            ]);
        }

        $user->email_verified_at = Carbon::now()->toDateTimeString();
        $user->verification_code = null;
        $user->save();

        $user->token = $user->createToken($request->device_name)->plainTextToken;
     
        return (new UserResource($user))
            ->response()
            ->setStatusCode(Response::HTTP_OK);

    }

    /**
     * 
     * @unauthenticated
     * Forgotten Password Method
     * 
     * 
     *
     */

    public function forgotPassword (Request $request)
    {
        $validated = $request->validate([
            'email'    => ['required', 'email'],
        ]);

        try {
            $user = User::find(13);
            $code = rand(1111,9999);
            $user->verification_code = $code;
            $user->save();

            dd($user);

            Mail::to($user)->send(new ForgottenPasswordVerification($user));

            return response('Email sent', 200);
        } catch(e) {
            return response('Unknown email', 400);
        }
    }
}
Sinnbeck's avatar

@aarontharker Sorry but I dont currently have any idea why it does it differently. Maybe some package or middleware is interfering.

aarontharker's avatar

@sinnbeck

I have gotten around it like this...

	$start = User::where('email', $validated['email'])->firstOrFail();
    $code = rand(1111,9999);
    $start->verification_code = $code;
    $start->save();
    $user = User::find($start->id);
aarontharker's avatar

@sinnbeck I still have no idea what is causing the behaviour but it appears every function I have that does a save() on a model, the only attributes available to that model afterwards are the ones that had been used in the update command to Postgres. After writing that it gave me an idea, caching. I will investigate further.

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@aarontharker Ah just noticed I made a mistake in my previous code

Try

$user = $user->fresh();
dd($user);
aarontharker's avatar

@sinnbeck ok that does work. Thanks I have never seen that method before but will remember it now :)

Sinnbeck's avatar

@aarontharker Refresh does a bit more. My original example would have worked if I had used that. The difference is if it mutates the $user, and if it loads relationships. So feel free to just use ->refresh() in this case

Sinnbeck's avatar

One last idea. Can you try this?

            $user = User::find(13);
            $code = rand(1111,9999);
            $user->update(['verification_code' => $code]);

            dd($user);

Please or to participate in this conversation.