model_dump() produces a dict of Python objects. A datetime field stays a datetime, a UUID stays a UUID, an Enum stays an enum member, and Decimal stays a Decimal. Passing that dict straight to json.dumps raises TypeError: Object of type datetime is not JSON serializable.
model_dump(mode="json") converts every value to something JSON accepts, using Pydantic's own serialisers, so datetimes become ISO 8601 strings and UUIDs become strings. model_dump_json() goes one step further and returns the encoded JSON string directly, which is faster because it serialises in Rust without building the intermediate dict.
Use mode="json" when the dict is going somewhere that will encode it later, such as a message queue payload or a template, and model_dump_json() when you want bytes or a string now. by_alias=True is usually wanted alongside either one, since aliases are otherwise only applied on input. exclude_none=True and exclude_unset=True are different: the first drops nulls, the second drops fields the caller never provided.